¿Es posible averiguar el tipo de parámetro y el tipo de retorno de una lambda?

135

Dada una lambda, ¿es posible descubrir su tipo de parámetro y el tipo de retorno? Si es así, ¿cómo?

Básicamente, quiero lambda_traitsque se pueda usar de las siguientes maneras:

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

La motivación detrás es que quiero usar lambda_traitsuna plantilla de función que acepte un lambda como argumento, y necesito saber su tipo de parámetro y el tipo de retorno dentro de la función:

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 el momento, podemos suponer que la lambda toma exactamente un argumento.

Inicialmente, intenté trabajar std::functioncomo:

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

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

Pero obviamente daría error. Entonces lo cambié a la TLambdaversión de la plantilla de función y quiero construir el std::functionobjeto dentro de la función (como se muestra arriba).

Nawaz
fuente
Si conoce el tipo de parámetro, esto se puede utilizar para determinar el tipo de retorno. Sin embargo, no sé cómo averiguar el tipo de parámetro.
Mankarse
¿Se supone que la función toma un solo argumento?
iammilind
1
"tipo de parámetro" Pero una función lambda arbitraria no tiene un tipo de parámetro. Podría tomar cualquier número de parámetros. Por lo tanto, cualquier clase de rasgos debería diseñarse para consultar parámetros por índices de posición.
Nicol Bolas
@iammilind: Sí. por el momento, podemos suponer eso.
Nawaz
@NicolBolas: Por el momento, podemos suponer que la lambda toma exactamente un argumento.
Nawaz

Respuestas:

160

Es curioso, acabo de escribir una function_traitsimplementación basada en Especializar una plantilla en una lambda en C ++ 0x que puede dar los tipos de parámetros. El truco, como se describe en la respuesta a esa pregunta, es usar el de la 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;
}

Tenga en cuenta que esta solución no funciona para lambda genérico como [](auto x) {}.

kennytm
fuente
Je, estaba escribiendo esto. Sin tuple_elementembargo, no pensé en eso , gracias.
GManNickG
@GMan: Si su enfoque no es exactamente el mismo, publíquelo entonces. Voy a probar esta solución.
Nawaz
3
Un rasgo completo también usaría una especialización para non- const, para aquellos lambda declarados mutable( []() mutable -> T { ... }).
Luc Danton
1
@Andry es un problema fundamental con los objetos de función que tienen (potencialmente) múltiples sobrecargas de operator()no con esta implementación. autono es un tipo, por lo que nunca puede ser la respuesta atraits::template arg<0>::type
Caleth
1
@helmesjo sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/… Como solución para los enlaces rotos: intente buscar desde la raíz, Luke.
Andry
11

Aunque no estoy seguro de que esto sea estrictamente estándar, ideone compiló el siguiente 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, "" );
}

Sin embargo, esto proporciona solo el tipo de función, por lo que los tipos de resultados y parámetros deben extraerse de él. Si puede usar boost::function_traits, result_typey arg1_type cumplirá el propósito. Dado que ideone parece no proporcionar impulso en el modo C ++ 11, no pude publicar el código real, lo siento.

Ise Wisteria
fuente
1
Creo que es un buen comienzo. +1 por eso. Ahora necesitamos trabajar en el tipo de función para extraer la información requerida. (No quiero usar Boost a partir de ahora, ya que quiero aprender las cosas).
Nawaz
6

El método de especialización que se muestra en la respuesta de @KennyTM se puede extender para cubrir todos los casos, incluidas las lambdas variables y mutables:

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)

Demostración .

Tenga en cuenta que la aridad no está ajustada para operator()s variadic . En cambio, uno también puede considerar is_variadic.

Columbo
fuente
1

La respuesta proporcionada por @KennyTMs funciona muy bien, sin embargo, si una lambda no tiene parámetros, el uso del índice arg <0> no se compila. Si alguien más tenía este problema, tengo una solución simple (más simple que usar soluciones relacionadas con SFINAE, es decir).

Simplemente agregue void al final de la tupla en la estructura arg después de los tipos de argumento variadic. es decir

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

Dado que la aridad no depende del número real de parámetros de plantilla, el real no será incorrecto, y si es 0, entonces al menos arg <0> seguirá existiendo y puede hacer lo que quiera. Si ya planea no superar el índice, arg<arity-1>entonces no debería interferir con su implementación actual.

Jon Koelzer
fuente