É possível descobrir o tipo de parâmetro e o tipo de retorno de um lambda?

135

Dado um lambda, é possível descobrir seu tipo de parâmetro e tipo de retorno? Se sim, como?

Basicamente, eu quero o lambda_traitsque pode ser usado das seguintes maneiras:

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

A motivação por trás disso é que eu quero usar lambda_traitsem um modelo de função que aceita um lambda como argumento, e preciso saber o tipo de parâmetro e o tipo de retorno dentro da função:

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!
   //...
}

Por enquanto, podemos assumir que o lambda leva exatamente um argumento.

Inicialmente, tentei trabalhar std::functioncomo:

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

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

Mas, obviamente, daria erro. Então eu mudei para a TLambdaversão do modelo de função e quero construir o std::functionobjeto dentro da função (como mostrado acima).

Nawaz
fonte
Se você conhece o tipo de parâmetro, isso pode ser usado para descobrir o tipo de retorno. Eu não sei como descobrir o tipo de parâmetro.
Mankarse 30/10
Supõe-se que a função tenha argumento único?
Izmilind 30/10
1
"tipo de parâmetro" Mas uma função lambda arbitrária não tem um tipo de parâmetro. Pode levar qualquer número de parâmetros. Portanto, qualquer classe de características teria que ser projetada para consultar parâmetros por índices de posição.
Nicol Bolas 30/10
@iammilind: Sim. por enquanto, podemos assumir isso.
Nawaz
@ NicolBolas: Por enquanto, podemos assumir que o lambda leva exatamente um argumento.
Nawaz

Respostas:

160

Engraçado, acabei de escrever uma function_traitsimplementação baseada em Especializando um modelo em um lambda em C ++ 0x, que pode fornecer os tipos de parâmetros. O truque, conforme descrito na resposta dessa pergunta, é usar o dos lambda . 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;
}

Observe que esta solução não funciona para lambda genérico como [](auto x) {}.

kennytm
fonte
Heh, eu estava escrevendo isso. Não pensei tuple_elementnisso, obrigado.
GManNickG 30/10
@ GMan: Se sua abordagem não for exatamente a mesma, publique-a então. Vou testar esta solução.
Nawaz
3
Uma característica completa também usaria uma especialização para non- const, para aqueles lambda declarados mutable( []() mutable -> T { ... }).
Luc Danton
1
@ Andy, esse é um problema fundamental com os objetos de função que possuem (potencialmente) várias sobrecargas ou operator()não nessa implementação. autonão é um tipo, por isso nunca pode ser a resposta paratraits::template arg<0>::type
Caleth
1
@helmesjo sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/… Como solução para links quebrados: tente pesquisar a partir da raiz, Luke.
Andry
11

Embora eu não tenha certeza de que isso seja estritamente padrão, o ideone compilou o seguinte código:

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, "" );
}

No entanto, isso fornece apenas o tipo de função, portanto, os tipos de resultado e parâmetro precisam ser extraídos dela. Se você pode usar boost::function_traits, result_typee arg1_type cumprirá o objetivo. Como o ideone parece não fornecer impulso no modo C ++ 11, não pude postar o código real, desculpe.

Ise Wisteria
fonte
1
Eu acho que é um bom começo. +1 para isso. Agora precisamos trabalhar no tipo de função para extrair as informações necessárias. (Não quero usar o Boost a partir de agora, pois quero aprender o material).
Nawaz
6

O método de especialização mostrado na resposta do @KennyTM pode ser estendido para cobrir todos os casos, incluindo lambdas variadas e mutáveis:

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 .

Observe que a aridade não é ajustada para operator()s variáveis . Em vez disso, também se pode considerar is_variadic.

Columbo
fonte
1

A resposta fornecida pelo @KennyTMs funciona muito bem, no entanto, se um lambda não tiver parâmetros, o uso do índice arg <0> não será compilado. Se alguém mais estava tendo esse problema, eu tenho uma solução simples (mais simples do que usar soluções relacionadas à SFINAE).

Apenas adicione void ao final da tupla na estrutura arg após os tipos de argumentos variados. ie

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

como a arity não depende do número real de parâmetros do modelo, o real não estará incorreto e, se for 0, pelo menos arg <0> ainda existirá e você poderá fazer o que quiser. Se você já planeja não exceder o índice arg<arity-1>, ele não deve interferir na sua implementação atual.

Jon Koelzer
fonte