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

135

Учитывая лямбду, возможно ли выяснить ее тип параметра и тип возвращаемого значения? Если да, то как?

В принципе, я хочу, lambda_traitsкоторый может быть использован следующими способами:

auto lambda = [](int i) { return long(i*10); };

lambda_traits<decltype(lambda)>::param_type  i; //i should be int
lambda_traits<decltype(lambda)>::return_type l; //l should be long

Мотивация заключается в том, что я хочу использовать lambda_traitsв шаблоне функции, который принимает лямбду в качестве аргумента, и мне нужно знать его тип параметра и тип возвращаемого значения внутри функции:

template<typename TLambda>
void f(TLambda lambda)
{
   typedef typename lambda_traits<TLambda>::param_type  P;
   typedef typename lambda_traits<TLambda>::return_type R;

   std::function<R(P)> fun = lambda; //I want to do this!
   //...
}

Пока можно предположить, что лямбда принимает ровно один аргумент.

Изначально я пытался работать с std::function:

template<typename T>
A<T> f(std::function<bool(T)> fun)
{
   return A<T>(fun);
}

f([](int){return true;}); //error

Но это, очевидно, даст ошибку. Поэтому я изменил его на TLambdaверсию шаблона функции и хочу создать std::functionобъект внутри функции (как показано выше).

Наваз
источник
Если вы знаете тип параметра, это можно использовать для определения типа возвращаемого значения. Я не знаю, как выяснить тип параметра, хотя.
Манкарс
Предполагается, что функция принимает один аргумент?
Iammilind
1
"тип параметра" Но произвольная лямбда-функция не имеет тип параметра. Это может принимать любое количество параметров. Таким образом, любой класс характеристик должен быть разработан для запроса параметров по индексам позиции.
Николь Болас
@iammilind: Да. пока что мы можем это предположить.
Наваз
@NicolBolas: В настоящее время мы можем предположить, что лямбда принимает ровно один аргумент.
Наваз

Ответы:

160

Забавно, я только что написал function_traitsреализацию, основанную на Специализации шаблона для лямбды в C ++ 0x, которая может давать типы параметров. Хитрость, как описано в ответе на этот вопрос, заключается в использовании лямбды . decltypeoperator()

template <typename T>
struct function_traits
    : public function_traits<decltype(&T::operator())>
{};
// For generic types, directly use the result of the signature of its 'operator()'

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
// we specialize for pointers to member function
{
    enum { arity = sizeof...(Args) };
    // arity is the number of arguments.

    typedef ReturnType result_type;

    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
        // the i-th argument is equivalent to the i-th tuple element of a tuple
        // composed of those arguments.
    };
};

// test code below:
int main()
{
    auto lambda = [](int i) { return long(i*10); };

    typedef function_traits<decltype(lambda)> traits;

    static_assert(std::is_same<long, traits::result_type>::value, "err");
    static_assert(std::is_same<int, traits::arg<0>::type>::value, "err");

    return 0;
}

Обратите внимание, что это решение не работает для общего лямбда, как [](auto x) {}.

kennytm
источник
Хех, я просто писал это. tuple_elementХотя не думал , спасибо.
GManNickG
@GMan: Если ваш подход не совсем такой, пожалуйста, опубликуйте его. Я собираюсь проверить это решение.
Наваз
3
Полная черта также будет использовать специализацию для non const, для тех лямбда объявляется mutable( []() mutable -> T { ... }).
Люк Дантон
1
@ Andry, это фундаментальная проблема с объектами функций, которые имеют (потенциально) многократные перегрузки operator()не с этой реализацией. autoэто не тип, поэтому он никогда не может быть ответомtraits::template arg<0>::type
Caleth
1
@helmesjo sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/… В качестве решения для неработающих ссылок: попробуйте выполнить поиск в корне, Люк.
Андри
11

Хотя я не уверен, что это строго соответствует стандартам, ideone скомпилировал следующий код:

template< class > struct mem_type;

template< class C, class T > struct mem_type< T C::* > {
  typedef T type;
};

template< class T > struct lambda_func_type {
  typedef typename mem_type< decltype( &T::operator() ) >::type type;
};

int main() {
  auto l = [](int i) { return long(i); };
  typedef lambda_func_type< decltype(l) >::type T;
  static_assert( std::is_same< T, long( int )const >::value, "" );
}

Однако это обеспечивает только тип функции, поэтому из него должны быть извлечены типы результатов и параметров. Если вы можете использовать boost::function_traits, result_typeи arg1_type будет соответствовать цели. Так как ideone, похоже, не обеспечивает повышение в режиме C ++ 11, я не смог опубликовать реальный код, извините.

Исе Вистерия
источник
1
Я думаю, это хорошее начало. +1 за это. Теперь нам нужно поработать над типом функции, чтобы извлечь необходимую информацию. (Я не хочу использовать Boost с тех пор, как я хочу изучить материал).
Наваз
6

Метод специализации, показанный в ответе @KennyTMs, может быть расширен, чтобы охватить все случаи, включая вариабельные и изменяемые лямбды:

template <typename T>
struct closure_traits : closure_traits<decltype(&T::operator())> {};

#define REM_CTOR(...) __VA_ARGS__
#define SPEC(cv, var, is_var)                                              \
template <typename C, typename R, typename... Args>                        \
struct closure_traits<R (C::*) (Args... REM_CTOR var) cv>                  \
{                                                                          \
    using arity = std::integral_constant<std::size_t, sizeof...(Args) >;   \
    using is_variadic = std::integral_constant<bool, is_var>;              \
    using is_const    = std::is_const<int cv>;                             \
                                                                           \
    using result_type = R;                                                 \
                                                                           \
    template <std::size_t i>                                               \
    using arg = typename std::tuple_element<i, std::tuple<Args...>>::type; \
};

SPEC(const, (,...), 1)
SPEC(const, (), 0)
SPEC(, (,...), 1)
SPEC(, (), 0)

Demo .

Обратите внимание, что арность не корректируется для вариационных operator()s. Вместо этого можно также рассмотреть is_variadic.

Columbo
источник
1

Ответ, предоставленный @KennyTMs, прекрасно работает, однако, если лямбда не имеет параметров, использование индекса arg <0> не компилируется. Если у кого-то еще была эта проблема, у меня есть простое решение (проще, чем использование решений, связанных с SFINAE).

Просто добавьте void в конец кортежа в структуре arg после типов аргументов variadic. т.е.

template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...,void>>::type type;
    };

поскольку арность не зависит от фактического количества параметров шаблона, фактические значения не будут неправильными, и если оно равно 0, то по крайней мере аргумент <0> все еще будет существовать, и вы можете делать с ним все, что захотите. Если вы уже планируете не превышать индекс, arg<arity-1>он не должен мешать вашей текущей реализации.

Джон Кельцер
источник