Разбираться с чужими трехэтажными шаблонами мне не нравится. Писать свои навороченные шаблонные конструкции тоже. Но иногда приходится это делать.
Вот сегодня захотелось сделать, чтобы можно было писать вот так:
// Лямбда с аргументом. Тип аргумента лямбды выводится автоматом. limit_then_transform( 1, []( const msg_request & request ) {...} ); // Лямбда без аргумента, тип нужно указывать явно. limit_then_transform< msg_get_status >( 1, [] {...} ); |
Соответственно, нужно было написать набор шаблонных функций для каждого из случаев. Вопрос был в том, как это сделать.
Можно было бы, наверное, написать совсем просто:
// Вариант для первого случая. template< typename S, typename R > transform_indicator_t< S > limit_then_transform( unsigned int limit, std::function< transformed_message< R >(const S &) > transformer ); // Вариант для второго случая. template< typename S, typename R > transform_indicator_t< S > limit_then_transform( unsigned int limit, std::function< transformed_message< R >() > transformer ); |
Но вот что очень сильно смущало: лямбда в C++ -- это не объект std::function. Это экземпляр какого-то анонимного класса, тип которого определяется самим компилятором. Лямбда-объект может трансформироваться в std::function, но это связано с некоторыми накладными расходами (в этом можно убедиться вот на этом примере). Поэтому хотелось работать именно с лямбдой, а не с std::function.
Поэтому нужно было сделать что-то вот такое:
template< typename LAMBDA > transform_indicator_t< /* какой-то тип */ > limit_then_transform( unsigned int limit, LAMBDA transformator ); template< typename SOURCE, typename LAMBDA > transform_indicator_t< SOURCE > limit_then_transform( unsigned int limit, LAMBDA transformator ); |
Соответственно, вопроса было два:
- Как указать компилятору тип возвращаемого значения для первой функции?
- Как заставить компилятор выбирать между двумя функциями, если вызов записывается вот так: limit_then_transform<some_type>?
В результате рыскания по Интернету и попыток восстановить в голове отрывочные знания C++, удалось прийти вот к чему:
template< typename LAMBDA > auto limit_then_transform( unsigned int limit, LAMBDA transformator ) -> transform_indicator_t< typename std::enable_if< has_func_call_operator< LAMBDA >::value, typename lambda_argument_if_lambda< LAMBDA >::type >::type >; template< typename SOURCE, typename LAMBDA > transform_indicator_t< SOURCE > limit_then_transform( unsigned int limit, LAMBDA transformator ); |
Но не оставляет сомнение, что перемудрил на ровном месте. Нужно будет вернуться к коду чуть попозже, на свежую голову.
Вообще, когда пишешь длинное шаблонное выражение, то складывается впечатление, что на Lisp-е код программируешь, только скобочки не круглые, а угловые. Поэтому жаль, что Vim по-умолчанию не показывает парность угловых скобочек в C++ном исходнике :(
А еще сегодня впервые задействовал std::enable_if. Чую, не к добру это ;)
Upd. Действительно, можно проще (см.PS). И без enable_if.
Ниже полный исходник. Часть кода (шаблоны message_type_only и lambda_traits) были взяты из исходников SObjectizer-а, поэтому там есть фрагменты, которые к данной проблеме отношения не имеют. Шаблоны has_func_call_operator, lambda_argument_if_lambda_impl и lambda_argument_if_lambda написаны сегодня в поисках решения.
#include <iostream> #include <memory> #include <functional> #include <type_traits> template< typename M > struct message_type_only { typedef typename std::remove_cv< typename std::remove_reference< M >::type >::type type; }; template< typename L > struct lambda_traits : public lambda_traits< decltype(&L::operator()) > {}; template< class L, class R, class M > struct lambda_traits< R (L::*)(M) const > { typedef R result_type; typedef typename message_type_only< M >::type argument_type; static R call_with_arg( L l, M m ) { return l(m); } }; template< class L, class R, class M > struct lambda_traits< R (L::*)(M) > { typedef R result_type; typedef typename message_type_only< M >::type argument_type; static R call_with_arg( L l, M m ) { return l(m); } }; template< typename L > class has_func_call_operator { private : template< typename P, P > struct checker; template< typename D > static std::true_type test( checker< decltype(&D::operator()), &D::operator()> * ); template< typename D > static std::false_type test(...); public : enum { value = std::is_same< std::true_type, decltype(test<L>(0)) >::value }; }; template< bool is_lambda, class L > struct lambda_argument_if_lambda_impl { using type = void; }; template< class L > struct lambda_argument_if_lambda_impl< true, L > { using type = typename lambda_traits< L >::argument_type; }; template< class L > struct lambda_argument_if_lambda : public lambda_argument_if_lambda_impl< has_func_call_operator< L >::value, L > {}; template< typename MSG > struct transform_indicator_t { unsigned int m_limit; transform_indicator_t( unsigned int limit ) : m_limit( limit ) {} }; template< typename LAMBDA > auto limit_then_transform( unsigned int limit, LAMBDA transformator ) -> transform_indicator_t< typename std::enable_if< has_func_call_operator< LAMBDA >::value, typename lambda_argument_if_lambda< LAMBDA >::type >::type > { using ARG = typename lambda_traits< LAMBDA >::argument_type; std::cout << "transformator_1 for: " << typeid(ARG).name() << std::endl; return transform_indicator_t< ARG >{ limit }; } template< typename SOURCE, typename LAMBDA > transform_indicator_t< SOURCE > limit_then_transform( unsigned int limit, LAMBDA transformator ) { std::cout << "transformator_2 for: " << typeid(SOURCE).name() << std::endl; return transform_indicator_t< SOURCE >{ limit }; } struct message_t { virtual ~message_t() {} }; struct request : public message_t { unsigned int m_id; }; struct negative_reply : public message_t { unsigned int m_id; negative_reply( unsigned int id ) : m_id( id ) {} }; struct overloaded : public message_t {}; void test() { auto f1 = []( const request & ) {}; auto t1 = limit_then_transform( 1, []( const request & ) {} ); auto t2 = limit_then_transform< overloaded >( 2, []{} ); } int main() { test(); } |
PS. Более простой вариант, без enable_if, использует вот эти определения:
template< bool is_lambda, class L > struct lambda_argument_if_lambda_impl {}; template< class L > struct lambda_argument_if_lambda_impl< true, L > { using type = typename lambda_traits< L >::argument_type; }; template< class L > struct lambda_argument_if_lambda : public lambda_argument_if_lambda_impl< has_func_call_operator< L >::value, L > {}; template< typename LAMBDA, typename ARG = typename lambda_argument_if_lambda< LAMBDA >::type > transform_indicator_t< ARG > limit_then_transform( unsigned int limit, LAMBDA transformator ) { std::cout << "transformator_1 for: " << typeid(ARG).name() << std::endl; return transform_indicator_t< ARG >{ limit }; } |
Комментариев нет:
Отправить комментарий