Можем ли мы получить тип лямбда-аргумента? - PullRequest
19 голосов
/ 28 июня 2011

Используя std::function, мы можем получить тип аргумента, используя argument_type, second_argument_type и т. Д., Но я не могу найти способ сделать то же самое с лямбдами.Является ли это возможным?(Я использую VS2010)

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

template<typename F> 
static void forward(F f)
{ 
    // Create an object of the type of the first
    // parameter to the function object F
    typedef typename F::argument_type T;
    T t;

    //...do something with 't' here (deserialize in my case)

    // Forward the object to the function
    f(t);
}

Это можно использоватьвот так и все работает нормально:

std::function<void(int)> f = [](int i) -> void { setValue(i); };
forward(f);

Но это не будет работать напрямую с лямбдами:

forward([](int i) -> void { setValue(i); });
//error C2039: 'argument_type' : is not a 
//member of '`anonymous-namespace'::<lambda1>'

Есть ли способ доступа к типам параметров таким образом, который будет работать дляоба лямбды и std::function объекты?Может быть, это способ получить сначала лямбду типа std::function, а затем argument_type из этого?


Исходя из ответа ниже, версия, которая работает с лямбдами и std::functionэто:

template<typename T, typename F> 
static void forward(F f)
{ 
    T t;

    //...do something with 't' here (deserialize in my case)

    f(t);
}

forward<int>([](int i) -> void { setValue(i); });

Так как int повторяется здесь, я надеялся избавиться от него - не так уж плохо для int, но больше раздражает для длинных имен типов в паре пространств имен.C'est la vie!

Ответы [ 2 ]

20 голосов
/ 28 июня 2011

Это нежелательно в общем случае.(Обратите внимание, что для std::function<T(A)> довольно просто указать, например, что такое argument_type: это просто A! Это доступно в определении типа.)

Можно потребовать каждый функциональный объектtype, чтобы указать типы аргументов, и, в свою очередь, предписывает, чтобы это делали типы замыкания, сгенерированные из лямбда-выражения.Фактически, функции до C ++ 0x, такие как адаптируемые функторы, работали бы только для таких типов.

Однако мы переходим к этому с C ++ 0x и по веским причинам.Самым простым из которых является просто перегрузка: тип функтора с шаблонным operator() (он же полиморфный функтор) просто принимает все виды аргументов;так что же должно быть * 1009?Другая причина заключается в том, что универсальный код (обычно) пытается указать наименьшие ограничения на типы и объекты, с которыми он работает, чтобы его было легче (повторно) использовать.

Другими словами, универсальный код не является действительно интересует, что с учетом Functor f, typename Functor::argument будет int. гораздо больше интересно знать, что f(0) является приемлемым выражением.Для этого C ++ 0x предоставляет такие инструменты, как decltype и std::declval (удобно упаковывать их вместе внутри std::result_of).

На мой взгляд, у вас есть два варианта: требовать, чтобы все функторы были переданываш шаблон использует соглашение в стиле C ++ 03, определяющее argument_type и т.п .;используйте технику ниже;или перепроектировать.Я бы порекомендовал последний вариант, но это ваш вызов, так как я не знаю, как выглядит ваша кодовая база или каковы ваши требования.


Для мономорфного типа функтора (т.е. без перегрузки) этоможно осмотреть operator() член.Это работает для типов закрытия лямбда-выражений.

Поэтому мы объявляем этих помощников

template<typename F, typename Ret, typename A, typename... Rest>
A
helper(Ret (F::*)(A, Rest...));

template<typename F, typename Ret, typename A, typename... Rest>
A
helper(Ret (F::*)(A, Rest...) const);

// volatile or lvalue/rvalue *this not required for lambdas (phew)

, которые принимают указатель на функцию-член, принимающую хотя бы один аргумент.А теперь:

template<typename F>
struct first_argument {
    typedef decltype( helper(&F::operator()) ) type;
};

[сложная черта может последовательно запрашивать перегрузки lvalue-rvalue / const / volatile и выставлять первый аргумент, если он одинаков для всех перегрузок, или использовать std::common_type.]

5 голосов
/ 11 февраля 2016

@ Ответ Люка великолепен, но я только что натолкнулся на случай, когда мне также нужно было иметь дело с указателями на функции:

template<typename Ret, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(*) (Arg, Rest...));

template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...));

template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...) const);

template <typename F>
decltype(first_argument_helper(&F::operator())) first_argument_helper(F);

template <typename T>
using first_argument = decltype(first_argument_helper(std::declval<T>()));

Это можно использовать как для функторов, так и для указателей функций:

void function(float);

struct functor {
    void operator() (int);
};

int main() {
    std::cout << std::is_same<first_argument<functor>, int>::value
              << ", "
              << std::is_same<first_argument<decltype(&function)>, int>::value 
              << std::endl;
    return 0;

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...