четверг, 5 марта 2015 г.

[prog.c++] В первый раз попробовал std::enable_if для перегрузки шаблона по типу аргумента

Разбираться с чужими трехэтажными шаблонами мне не нравится. Писать свои навороченные шаблонные конструкции тоже. Но иногда приходится это делать.

Вот сегодня захотелось сделать, чтобы можно было писать вот так:

// Лямбда с аргументом. Тип аргумента лямбды выводится автоматом.
limit_then_transform( 1, []( const msg_request & request ) {...} );

// Лямбда без аргумента, тип нужно указывать явно.
limit_then_transform< msg_get_status >( 1, [] {...} );

Соответственно, нужно было написать набор шаблонных функций для каждого из случаев. Вопрос был в том, как это сделать.

Можно было бы, наверное, написать совсем просто:

// Вариант для первого случая.
templatetypename S, typename R >
transform_indicator_t< S >
limit_then_transform(
   unsigned int limit,
   std::function< transformed_message< R >(const S &) > transformer );

// Вариант для второго случая.
templatetypename 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.

Поэтому нужно было сделать что-то вот такое:

templatetypename LAMBDA >
transform_indicator_t< /* какой-то тип */ >
limit_then_transform(
   unsigned int limit,
   LAMBDA transformator );

templatetypename SOURCE, typename LAMBDA >
transform_indicator_t< SOURCE >
limit_then_transform(
   unsigned int limit,
   LAMBDA transformator );

Соответственно, вопроса было два:

  1. Как указать компилятору тип возвращаемого значения для первой функции?
  2. Как заставить компилятор выбирать между двумя функциями, если вызов записывается вот так: limit_then_transform<some_type>?

В результате рыскания по Интернету и попыток восстановить в голове отрывочные знания C++, удалось прийти вот к чему:

templatetypename 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 >;

templatetypename 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>

templatetypename M >
struct message_type_only
   {
      typedef typename std::remove_cv<
            typename std::remove_reference< M >::type >::type type;
   };

templatetypename L >
struct lambda_traits
   :  public lambda_traits< decltype(&L::operator()) >
   {};

templateclass 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);
         }
   };

templateclass 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);
         }
   };

templatetypename L >
class has_func_call_operator
   {
   private :
      templatetypename P, P > struct checker;

      templatetypename D > static std::true_type test(
            checker< decltype(&D::operator()), &D::operator()> * );

      templatetypename D > static std::false_type test(...);

   public :
      enum { value =
         std::is_same< std::true_type, decltype(test<L>(0)) >::value };
   };

templatebool is_lambda, class L >
struct lambda_argument_if_lambda_impl
   {
      using type = void;
   };

templateclass L >
struct lambda_argument_if_lambda_impl< true, L >
   {
      using type = typename lambda_traits< L >::argument_type;
   };

templateclass L >
struct lambda_argument_if_lambda
   :  public lambda_argument_if_lambda_impl<
         has_func_call_operator< L >::value, L >
   {};

templatetypename MSG >
struct transform_indicator_t
   {
      unsigned int m_limit;

      transform_indicator_t( unsigned int limit ) : m_limit( limit ) {}
   };

templatetypename 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 };
   }

templatetypename 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, использует вот эти определения:

templatebool is_lambda, class L >
struct lambda_argument_if_lambda_impl
   {};

templateclass L >
struct lambda_argument_if_lambda_impl< true, L >
   {
      using type = typename lambda_traits< L >::argument_type;
   };

templateclass 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 };
   }

Комментариев нет: