Можем ли мы получить тип аргумента лямбда?
Используя std::function
, мы можем получить тип аргумента с использованием typedefs argument_type
, second_argument_type
и т.д., но я не вижу способа сделать то же самое с lambdas. Является ли это возможным? (Я использую VS2010)
Скажем, я хочу, чтобы в моей системе десериализации, используемой для чтения объекта, и для передачи его в функцию setter, нужно следующее:
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);
Но он не будет работать напрямую с lambdas:
forward([](int i) -> void { setValue(i); });
//error C2039: 'argument_type' : is not a
//member of '`anonymous-namespace'::<lambda1>'
Есть ли способ доступа к типам параметров таким образом, который будет работать как для объектов lambdas, так и для std::function
? Может быть, способ сначала получить тип std::function
лямбда, а затем argument_type
от этого?
Следуя приведенному ниже ответу, версия, которая работает с lambdas и 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!
Ответы
Ответ 1
Это нежелательно в общем случае. (Обратите внимание, что для std::function<T(A)>
достаточно просто указать, что, например, argument_type
: это просто A
! Оно доступно в определении типа.)
Можно было бы потребовать, чтобы каждый тип объекта функции указывал свои типы аргументов и, в свою очередь, указывал на то, что типы замыкания, генерируемые из лямбда-выражения, делают это. Фактически, функции pre-С++ 0x, такие как адаптируемые функторы, будут работать только для таких типов.
Однако мы переходим от этого с С++ 0x и по уважительным причинам. Самый простой из них - просто перегрузка: тип функтора с шаблоном operator()
(a.k.a - полиморфный функтор) просто принимает всевозможные аргументы; так что должно быть argument_type
? Другая причина заключается в том, что общий код (обычно) пытается определить наименьшие ограничения для типов и объектов, на которых он работает, для более простого использования (re).
Другими словами, общий код действительно не интересует то, что данный Functor f
, typename Functor::argument
be int
. Намного интереснее знать, что f(0)
является приемлемым выражением. Для этого С++ 0x предоставляет такие инструменты, как decltype
и std::declval
(удобно упаковывая два внутри std::result_of
).
Как я вижу, у вас есть два варианта: требуется, чтобы все функторы, переданные в ваш шаблон, использовали соглашение типа С++ 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 overloads и выставлять первый аргумент, если он одинаковый для всех перегрузок, или использовать std::common_type
.]
Ответ 2
@Luc ответ велик, но я просто наткнулся на случай, когда мне также нужно было иметь дело с указателями функций:
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;
}