Специализирую шаблон на лямбде в C++0x

Я написал класс черт, который позволяет мне извлекать информацию об аргументах и ​​типе функции или функционального объекта в 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 ответа

Решение

Я думаю, что можно специализировать черты для лямбд и сопоставлять образцы по сигнатуре безымянного функтора. Вот код, который работает на 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; });
}

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(), Если вы пересмотрите свой дизайн, вы найдете другой, более гибкий подход.

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

Во-первых, я должен сказать, что соответствующий метод 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>() ) )
{
};
Другие вопросы по тегам