Специализирую шаблон на лямбде в C ++ 0x - PullRequest
24 голосов
/ 01 апреля 2010

Я написал класс черт, который позволяет мне извлекать информацию об аргументах и ​​типе функции или функционального объекта в C ++ 0x (протестировано с gcc 4.5.0). Общий случай обрабатывает функциональные объекты:

template <typename F>
struct function_traits {
    template <typename R, typename... A>
    struct _internal { };

    template <typename R, typename... A>
    struct _internal<R (F::*)(A...)> {
        // ...
    };

    typedef typename _internal<decltype(&F::operator())>::<<nested types go here>>;
};

Тогда у меня есть специализация для простых функций в глобальной области видимости:

template <typename R, typename... A>
struct function_traits<R (*)(A...)> {
    // ...
};

Это отлично работает, я могу передать функцию в шаблон или объект функции, и она работает правильно:

template <typename F>
void foo(F f) {
    typename function_traits<F>::whatever ...;
}

int f(int x) { ... }
foo(f);

Что если вместо передачи функции или объекта функции в foo я хочу передать лямбда-выражение?

foo([](int x) { ... });

Проблема здесь в том, что ни одна специализация function_traits<> не применима. В проекте C ++ 0x говорится, что тип выражения является «уникальным, безымянным, не объединенным типом класса». Обработка результата вызова typeid(...).name() в выражении дает мне то, что кажется внутренним соглашением именования gcc для лямбды, main::{lambda(int)#1}, а не то, что синтаксически представляет типовое имя C ++.

Короче, могу ли я что-нибудь добавить в шаблон здесь:

template <typename R, typename... A>
struct function_traits<????> { ... }

что позволит классу черт принимать лямбда-выражение?

Ответы [ 3 ]

18 голосов
/ 02 апреля 2010

Я думаю, что можно специализировать черты для лямбд и сопоставлять шаблоны по сигнатуре безымянного функтора. Вот код, который работает на g ++ 4.5. Хотя это работает, сопоставление с образцом в лямбде, кажется, работает вопреки интуиции. У меня есть комментарии.

struct X
{
  float operator () (float i) { return i*2; }
  // If the following is enabled, program fails to compile
  // mostly because of ambiguity reasons.
  //double operator () (float i, double d) { return d*f; } 
};

template <typename T>
struct function_traits // matches when T=X or T=lambda
// As expected, lambda creates a "unique, unnamed, non-union class type" 
// so it matches here
{
  // Here is what you are looking for. The type of the member operator()
  // of the lambda is taken and mapped again on function_traits.
  typedef typename function_traits<decltype(&T::operator())>::return_type return_type;
};

// matches for X::operator() but not of lambda::operator()
template <typename R, typename C, typename... A>
struct function_traits<R (C::*)(A...)> 
{
  typedef R return_type;
};

// I initially thought the above defined member function specialization of 
// the trait will match lambdas::operator() because a lambda is a functor.
// It does not, however. Instead, it matches the one below.
// I wonder why? implementation defined?
template <typename R, typename... A>
struct function_traits<R (*)(A...)> // matches for lambda::operator() 
{
  typedef R return_type;
};

template <typename F>
typename function_traits<F>::return_type
foo(F f)
{
  return f(10);
}

template <typename F>
typename function_traits<F>::return_type
bar(F f)
{
  return f(5.0f, 100, 0.34);
}

int f(int x) { return x + x;  }

int main(void)
{
  foo(f);
  foo(X());
  bar([](float f, int l, double d){ return f+l+d; });
}
2 голосов
/ 27 сентября 2016

Трюк void_t может помочь. Как работает `void_t` ?

Если у вас нет C ++ 17, вам нужно будет включить определение void_t:

template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

Добавить дополнительный аргумент шаблона в исходный шаблон, по умолчанию void:

template <typename T, typename = void>
struct function_traits;

Объект черт для простых функций такой же, как у вас уже есть:

template <typename R, typename... A>
struct function_traits<R (*)(A...)>
{
    using return_type = R;
    using class_type  = void;
    using args_type   = std:: tuple< A... >;
};

Для неконстантных методов:

template <typename R, typename... A>
struct function_traits<R (*)(A...)>
{
    using return_type = R;
    using class_type  = void;
    using args_type   = std:: tuple< A... >;
};

Не забудьте const методы:

template <typename R, typename C, typename... A>
struct function_traits<R (C::*)(A...) const> // const
{
    using return_type = R;
    using class_type  = C;
    using args_type   = std:: tuple< A... >;
};

Наконец, важная черта. Учитывая тип класса, включая лямбда-типы, мы хотим переместиться от T до decltype(&T::operator()). Мы хотим убедиться, что эта черта доступна только для типов T, для которых доступна ::operator(), и это то, что void_t делает для нас. Чтобы применить это ограничение, нам нужно поместить &T::operator() в сигнатуру признака где-то, следовательно, template <typename T> struct function_traits<T, void_t< decltype(&T::operator())

template <typename T>
struct   function_traits<T, void_t< decltype(&T::operator()) > > 
: public function_traits<           decltype(&T::operator())   >
{
};

Метод operator () в (не mutable, не универсальных) лямбдах - const, что объясняет, почему нам нужен шаблон const выше.

Но в конечном итоге это очень ограничительно. Это не будет работать с общими лямбдами или объектами с шаблонами operator(). Если вы пересмотрите свой дизайн, вы найдете другой, более гибкий подход.

1 голос
/ 22 сентября 2016

Передав часть работы серии шаблонов функций вместо шаблона класса , вы можете извлечь соответствующую информацию.

Во-первых, я должен сказать, что соответствующий метод - это const метод для лямбды (для не захватывающей, неуниверсальной, не mutable лямбды). Таким образом, вы не сможете определить разницу между настоящей лямбдой и этим:

struct {
    int operator() (int) const { return 7; }
} object_of_unnamed_name_and_with_suitable_method;

Поэтому я должен предположить, что вам не нужна «специальная обработка» для лямбд, и вы не хотите проверять, является ли тип типом лямбда, и что вместо этого вы хотите просто извлечь тип возвращаемого значения и тип всех аргументов для любого достаточно простого объекта. Под «достаточно простым» я имею в виду, например, что метод operator() является , а не сам по себе шаблоном. И, для информации о бонусе, логическое значение, сообщающее нам, присутствовал и использовался ли метод operator(), в отличие от простой старой функции.



// First, a convenient struct in which to store all the results:
template<bool is_method_, bool is_const_method_, typename C, typename R, typename ...Args>
struct function_traits_results {
    constexpr static bool is_method = is_method_;
    constexpr static bool is_const_method = is_const_method_;
    typedef C class_type; // void for plain functions. Otherwise,
                          // the functor/lambda type
    typedef R return_type;
    typedef tuple<Args...> args_type_as_tuple;
};

// This will extract all the details from a method-signature:
template<typename>
struct intermediate_step;
template<typename R, typename C, typename ...Args>
struct intermediate_step<R (C::*) (Args...)>  // non-const methods
    : public function_traits_results<true, false, C, R, Args...>
{
};
template<typename R, typename C, typename ...Args>
struct intermediate_step<R (C::*) (Args...) const> // const methods
    : public function_traits_results<true, true, C, R, Args...>
{
};


// These next two overloads do the initial task of separating
// plain function pointers for functors with ::operator()
template<typename R, typename ...Args>
function_traits_results<false, false, void, R, Args...>
function_traits_helper(R (*) (Args...) );
template<typename F, typename ..., typename MemberType = decltype(&F::operator()) >
intermediate_step<MemberType>
function_traits_helper(F);


// Finally, the actual `function_traits` struct, that delegates
// everything to the helper
template <typename T>
struct function_traits : public decltype(function_traits_helper( declval<T>() ) )
{
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...