Чи можна визначити тип параметра і тип повернення лямбда?


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) {}.


Хе, я тільки писав це. Не думав, tuple_elementхоча, спасибі.
GManNickG

@GMan: Якщо ваш підхід не зовсім такий, як цей, будь ласка, опублікуйте його тоді. Я збираюся протестувати це рішення.
Наваз

3
Повна ознака також використовує спеціалізацію для тих 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 .

Зауважте, що параметр arity не коригується для варіантів operator()s. Натомість можна також розглянути is_variadic.


1

Відповідь, надана @KennyTMs, чудово працює, проте якщо лямбда не має параметрів, використання індексу arg <0> не компілюється. Якщо хтось інший мав цю проблему, у мене є просте рішення (простіше, ніж використання пов'язаних з SFINAE рішень, тобто).

Просто додайте недійсність до кінця кортежу в структурі аргументів після варіантів аргументів. тобто

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.