Можем ли мы получить тип аргумента лямбда?

Используя 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;

}