Is it possible to figure out the parameter type and return type of a lambda?

C++LambdaMetaprogrammingC++11Traits

C++ Problem Overview


Given a lambda, is it possible to figure out it's parameter type and return type? If yes, how?

Basically, I want lambda_traits which can be used in following ways:

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

The motivation behind is that I want to use lambda_traits in a function template which accepts a lambda as argument, and I need to know it's parameter type and return type inside the function:

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

For the time being, we can assume that the lambda takes exactly one argument.

Initially, I tried to work with std::function as:

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

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

But it obviously would give error. So I changed it to TLambda version of the function template and want to construct the std::function object inside the function (as shown above).

C++ Solutions


Solution 1 - C++

Funny, I've just written a function_traits implementation based on https://stackoverflow.com/questions/2562320/specializing-a-template-on-a-lambda-in-c0x which can give the parameter types. The trick, as described in the answer in that question, is to use the decltype of the lambda's operator().

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;
}

Note that this solution does not work for generic lambda like [](auto x) {}.

Solution 2 - C++

Though I'm not sure this is strictly standard conforming, ideone compiled the following code:

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

However, this provides only the function type, so the result and parameter types have to be extracted from it. If you can use boost::function_traits, result_type and arg1_type will meet the purpose. Since ideone seems not to provide boost in C++11 mode, I couldn't post the actual code, sorry.

Solution 3 - C++

The specialization method shown in @KennyTMs answer can be extended to cover all cases, including variadic and mutable lambdas:

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.

Note that the arity is not adjusted for variadic operator()s. Instead one can also consider is_variadic.

Solution 4 - C++

The answer provided by @KennyTMs works great, however if a lambda has no parameters, using the index arg<0> does not compile. If anyone else was having this problem, I have a simple solution (simpler than using SFINAE related solutions, that is).

Just add void to the end of the tuple in the arg struct after the variadic argument types. i.e.

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

since the arity isn't dependent on the actual number of template parameters, the actual won't be incorrect, and if it's 0 then at least arg<0> will still exist and you can do with it what you will. If you already plan to not exceed the index arg<arity-1> then it shouldn't interfere with your current implementation.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNawazView Question on Stackoverflow
Solution 1 - C++kennytmView Answer on Stackoverflow
Solution 2 - C++Ise WisteriaView Answer on Stackoverflow
Solution 3 - C++ColumboView Answer on Stackoverflow
Solution 4 - C++Jon KoelzerView Answer on Stackoverflow