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

Resuelto Sarfaraz Nawaz asked hace 12 años • 6 respuestas

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

Básicamente, quiero lambda_traitsque se pueda utilizar 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 una lambda como argumento, y necesito saber su tipo de parámetro y 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 lambda toma exactamente un argumento.

Inicialmente, intenté trabajar con 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. Así que lo cambié a TLambdala versión de la plantilla de función y quiero construir el std::functionobjeto dentro de la función (como se muestra arriba).

Sarfaraz Nawaz avatar Oct 30 '11 12:10 Sarfaraz Nawaz
Aceptado

Es curioso, acabo de escribir una function_traitsimplementación basada en la especialización de una plantilla en una lambda en C++0x que puede proporcionar los tipos de parámetros. El truco, como se describe en la respuesta a esa pregunta, es utilizar la función 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érica como [](auto x) {}.

kennytm avatar Oct 30 '2011 07:10 kennytm

Aunque no estoy seguro de que esto cumpla estrictamente con los estándares, 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 ella. Si puede utilizarlo boost::function_traits, result_typecumplirá arg1_type 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 avatar Oct 30 '2011 07:10 Ise Wisteria

El método de especialización que se muestra en la respuesta de @KennyTM se puede ampliar 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 se ajusta por variadic operator()s. En cambio también se puede considerar is_variadic.

Columbo avatar Jan 29 '2015 11:01 Columbo

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 (es decir, más simple que usar soluciones relacionadas con SFINAE).

Simplemente agregue void al final de la tupla en la estructura arg después de los tipos de argumentos variados. 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 la plantilla, el real no será incorrecto y, si es 0, al menos arg<0> seguirá existiendo y podrás hacer con él lo que quieras. Si ya planea no exceder el índice, arg<arity-1>entonces no debería interferir con su implementación actual.

Jon Koelzer avatar May 10 '2018 10:05 Jon Koelzer

Aquí está mi versión de juguete de los rasgos funcionales. Aproveché std::functionla inferencia de tipos sin construir ningún std::functionobjeto.

Esto permite trabajar con funciones regulares, no solo con functores.


template <typename Return, typename... Args>
struct FunctionTraits_ {
  FunctionTraits_(std::function<Return(Args...)> f) {}
  using return_type = Return;
  using arg_types = std::tuple<Args...>;

  enum { arity = sizeof...(Args) };
  template <size_t i>
  struct arg_type {
    using type = typename std::tuple_element<i, arg_types>::type;
  };
};

template <typename Function>
struct FunctionTraits : public decltype(FunctionTraits_(
                            std::function{std::declval<Function>()})) {};

// Testing
std::size_t func(int, const std::string &s) { return s.size(); }
auto lambda = [](int, const std::string &s) { return s.size(); };

int main() {
  using traits = FunctionTraits<decltype(func)>;
  // using traits = FunctionTraits<decltype(lambda)>;

  static_assert(traits::arity == 2);
  static_assert(std::is_same_v<traits::return_type, std::size_t>);
  static_assert(std::is_same_v<traits::arg_type<0>::type, int>);
  static_assert(std::is_same_v<traits::arg_type<1>::type, const std::string &>);
  static_assert(std::is_same_v<traits::arg_types, std::tuple<int, const std::string &>>);

  return 0;
}

Gracias, Cœur , por la inspiración.

Nikolay Sheyko avatar Dec 24 '2023 19:12 Nikolay Sheyko