147

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œur
  • 37,241
  • 25
  • 195
  • 267
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • If you know the parameter type then [this](http://stackoverflow.com/questions/7260561/extracting-the-return-type-from-an-overloaded-function/7260753#7260753) can be used to figure out the return type. I don't know how to figure out the parameter type though. – Mankarse Oct 30 '11 at 05:57
  • Is it assumed that function takes single argument ? – iammilind Oct 30 '11 at 06:01
  • 1
    "parameter type" But an arbitrary lambda function doesn't have a parameter type. It could take any number of parameters. So any traits class would have to be designed to query parameters by position indices. – Nicol Bolas Oct 30 '11 at 06:01
  • @iammilind: Yes. for the time being, we can assume that. – Nawaz Oct 30 '11 at 06:03
  • @NicolBolas: For the time being, we can assume that the lambda takes exactly one argument. – Nawaz Oct 30 '11 at 06:03
  • "The motivation behind is that I want to use lambda_traits in a function template which accepts a lambda as argument" Why would you want it to _only_ accept a lambda? Wouldn't it make more sense to just use a `std::function`? Do you really want to force the user to only ever call this with specifically a lambda functor? – Nicol Bolas Oct 30 '11 at 06:04
  • @NicolBolas: Because `std::function` doesn't seem to work: http://www.ideone.com/E1Iqf – Nawaz Oct 30 '11 at 06:08

5 Answers5

181

Funny, I've just written a function_traits implementation based on Specializing a template on a lambda in C++0x 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) {}.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • Heh, I was just writing this. Didn't think about `tuple_element` though, thanks. – GManNickG Oct 30 '11 at 07:30
  • @GMan: If your approach is not exactly same as this, please post it then. I'm going to test this solution. – Nawaz Oct 30 '11 at 07:33
  • @Nawaz: The utility Kenny linked to is nearly identical to my own utility I have for my projects. (I just have to Boost.PP iterate since I'm stuck in MSVC C++0x support.) The only thing is I wrote a `get_nth_element` meta-function, instead of (ab)using `tuple_element`. – GManNickG Oct 30 '11 at 08:29
  • 3
    A complete trait would also use a specialization for non-`const`, for those lambda declared `mutable` (`[]() mutable -> T { ... }`). – Luc Danton Oct 30 '11 at 10:14
  • Try the function_traits specialization first and then #include . The compiler goes bonkers. – cdiggins Jan 13 '14 at 01:29
  • It might also be useful to include `typedef ReturnType(simple_function_type)(Args...)` into the function_traits class. And also, perhaps `typedef std::function std_function_type;` – Aaron McDaid Sep 03 '14 at 22:50
  • A follow up question here https://stackoverflow.com/questions/48078718/getting-lambda-arguments-types-inside-function-is-not-working – e271p314 Jan 03 '18 at 13:49
  • This does not work in case where a lambda has auto arguments: `[](auto i)`. The error states for msvc2015: `error C3556: 'ttt::::operator ()': incorrect argument to 'decltype'` or clang4.0: `reference to overloaded function could not be resolved`. I know this is legit error, but even so the arity is not reachable because of the error. – Andry Jan 06 '18 at 21:15
  • 1
    @Andry that's a fundamental problem with function objects that have (potentially) multiple overloads of `operator()` not with this implementation. `auto` is not a type, so it can't ever be the answer to `traits::template arg<0>::type` – Caleth Jan 16 '18 at 12:19
  • @Andry e.g. what is the arity of `struct functor { void operator()(int) {}; void operator()(int, double, bool) {} };`? – Caleth Jan 16 '18 at 12:22
  • @Caleth I agree, in overloaded case you can not find the arity. But in case of lambda `[](auto i)` the arity is known, because you have single operator with templated arguments and you still can not get it's quantity. And even more, you have to check the object on callability, before access the arity, so described traits implementation is not enough in this case. – Andry Jan 17 '18 at 13:43
  • @Andry that's overloaded, in the general case – Caleth Jan 17 '18 at 13:45
  • @Caleth Templated != Overloaded – Andry Jan 17 '18 at 13:46
  • You can *use* that lambda in multiple contexts and `i` will be deduced as multiple types. The class synthesised will have multiple overloads of `operator()`. I don't know of any method of finding out if all the overloads of a given function have the same arity, which is what you would need. – Caleth Jan 17 '18 at 13:49
  • Why it should have multiple overloads? – Andry Jan 17 '18 at 13:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/163330/discussion-between-caleth-and-andry). – Caleth Jan 17 '18 at 13:50
  • If somebody has interested in better implementation: https://svn.code.sf.net/p/tacklelib/tacklelib/trunk/utility/type_traits.hpp – Andry Jan 31 '18 at 14:17
  • @Andry Page is gone... Got a spare? – helmesjo Jan 16 '19 at 17:49
  • 1
    @helmesjo https://sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/utility/type_traits.hpp As a solution for broken links: try to search from the root, Luke. – Andry Jan 17 '19 at 07:59
12

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.

Ise Wisteria
  • 11,259
  • 2
  • 43
  • 26
  • 1
    I think, it is a good start. +1 for that. Now we need to work on function type to extract the required information. (I don't want to use Boost as of now, as I want to *learn* the stuffs). – Nawaz Oct 30 '11 at 07:22
9

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.

Columbo
  • 60,038
  • 8
  • 155
  • 203
2

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.

Jon Koelzer
  • 350
  • 2
  • 9
0

If you're looking for a complete solution for all types in C++ that can be invoked, a lot of these answers work but miss some of the corner cases, like

  • A reference to a lambda
  • Functions and function pointers

Here's a complete solution to my knowledge (except for generic lambdas) - let me know in the comments if anything is missing:

template <typename>
struct closure_traits;

template <typename FunctionT> // overloaded operator () (e.g. std::function)
struct closure_traits
    : closure_traits<decltype(&std::remove_reference_t<FunctionT>::operator())>
{
};

template <typename ReturnTypeT, typename... Args> // Free functions
struct closure_traits<ReturnTypeT(Args...)>
{
    using arguments = std::tuple<Args...>;

    static constexpr std::size_t arity = std::tuple_size<arguments>::value;

    template <std::size_t N>
    using argument_type = typename std::tuple_element<N, arguments>::type;

    using return_type = ReturnTypeT;
};

template <typename ReturnTypeT, typename... Args> // Function pointers
struct closure_traits<ReturnTypeT (*)(Args...)>
    : closure_traits<ReturnTypeT(Args...)>
{
};

// member functions
template <typename ReturnTypeT, typename ClassTypeT, typename... Args>
struct closure_traits<ReturnTypeT (ClassTypeT::*)(Args...)>
    : closure_traits<ReturnTypeT(Args...)>
{
    using class_type = ClassTypeT;
};

// const member functions (and lambda's operator() gets redirected here)
template <typename ReturnTypeT, typename ClassTypeT, typename... Args>
struct closure_traits<ReturnTypeT (ClassTypeT::*)(Args...) const>
    : closure_traits<ReturnTypeT (ClassTypeT::*)(Args...)>
{
};

Disclaimer: the std::remove_reference was inspired by this code.

Luka Govedič
  • 399
  • 4
  • 14
  • 1
    Covers most mainstream scenarios but not a complete solution (see my own production grade, fully documented solution at https://github.com/HexadigmAdmin/FunctionTraits). You're missing (in no particular order) specializations that handle noexcept (part of a function's type since C++17 - this is a must-have), "volatile" cv-qualifier on non-static member functions (though very rare in the real world), ref-qualifiers on member functions (again, very rare), variadic functions (those with "..." args), and calling conventions (only the default will be detected - "cdecl" usually). – Larry May 15 '23 at 11:43
  • Also, function pointers with cv-qualifiers won't be detected (caller has to invoke "std::remove_cv_t" first - not a showstopper but a bit inconvenient) – Larry May 15 '23 at 11:55