20

I was wondering if there is a standard way to get the type signature (i.e. the return type and the types) of its parameters of any given lambda?

The reason I ask is that I've always wondered what exactly is the type auto in the declaration like auto l =[](int x,int y)->int{return x+y;}. In other use cases of auto, it's a convenience and shorter alternative for a longer type name. But for lambdas, is there even an alternative way to declare the lambda variable?

My understanding is that a standard lambda is nothing more than a function object, and it is its own type. So, even if two lambdas have the same return type and parameter types, they are still two different, unrelated classes/functors. But this there a way to capture the fact that they are the same in terms of type signature?

I think the type signature I am looking for can be something like a std::function<> object of the correct types.

A more useful/involved question is, if it's possible to extract the type signature, this is possible to write a general wrapper function to convert any lambda function to a std::function object of the same type signature.

thor
  • 21,418
  • 31
  • 87
  • 173

4 Answers4

18

You are correct the types of C++11 lambdas are anonymous and instance-unique. the std::function type can store references to any kind of lambda I have come across, but there is said to be a performance hit.

Try

std::function<int (int, int)> f = [](int x, int y) -> int { 
    return x + y; 
};

note the -> int can be omitted in non ambiguous scenarios such as this.

C++14 lets us write

std::function<int (int, int)> f = [](auto x, auto y) { 
    return x + y; 
};

which is handy for long type names.

As noted by @Jonathan Wakely, this approach captures a specific instantiation using std::function with fixed template arguments. In C++14, template variables can be specified. Additionally, also per C++14, lambda parameters can have can have their types inferred via auto, allowing for the following:

template<class T>
std::function<T (T, T)> g = [](auto x, auto y) -> auto {
    return x + y;
};

Currently, VC++, and GCC do not seem to support templates on variable declarations at function level, but allow them on member, namespace, and global declarations. I am unsure whether or not this restriction emanates from the spec.

Note: I do not use clang.

Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
  • A `std::function` has a fixed call signature (e.g. `int(int,int)` in your example) whereas a generic lambda's closure type might accept different arguments, e.g. `[](auto... x) { }`), so although you can store it in a `std::function` you are limiting its functionality – Jonathan Wakely Feb 09 '14 at 14:41
  • 1
    I wasn't taking into account the generic lambdas in the upcoming standard. However, i have used std::function instances to pass closures to other functions. But actually as they are planning to add templatized variable declarations, in the next standard, one should be able to write: `template std::function< T (T, T) > f = (auto x,auto y) {return x+y;};` – Aluan Haddad Feb 09 '14 at 14:44
  • 1
    Interesting, I hadn't thought of using a variable template like that. – Jonathan Wakely Feb 09 '14 at 14:48
  • Yeah its a weird new feature. Especially since the auto keyword only when used as the type of an argument, follows the type inference rules of templates, not those of auto variable declarations. – Aluan Haddad Feb 09 '14 at 14:51
17

According to Can the 'type' of a lambda expression be expressed?, there is actually a simple way in current c++ (without needing c++1y) to figure out the return_type and parameter types of a lambda. Adapting this, it is not difficult to assemble a std::function typed signature type (called f_type below) for each lambda.

I. With this abstract type, it is actually possible to have an alternative way to auto for expressing the type signature of a lambda, namely function_traits<..>::f_type below. Note: the f_type is not the real type of a lambda, but rather a summary of a lambda's type signature in functional terms. It is however, probably more useful than the real type of a lambda because every single lambda is its own type.

As shown in the code below, just like one can use vector<int>::iterator_type i = v.begin(), one can also do function_traits<lambda>::f_type f = lambda, which is an alternative to the mysterious auto. Of course, this similarity is only formal. The code below involves converting the lambda to a std::function with the cost of type erasure on construction of std::function object and a small cost for making indirect call through the std::function object. But these implementation issues for using std::function aside (which I don't believe are fundamental and should stand forever), it is possible, after all, to explicitly express the (abstract) type signature of any given lambda.

II. It is also possible to write a make_function wrapper (pretty much like std::make_pair and std::make_tuple) to automatically convert a lambda f ( and other callables like function pointers/functors) to std::function, with the same type-deduction capabilities.

Test code is below:

#include <cstdlib>
#include <tuple>
#include <functional>
#include <iostream>
using namespace std;

// For generic types that are functors, delegate to its 'operator()'
template <typename T>
struct function_traits
    : public function_traits<decltype(&T::operator())>
{};

// for pointers to member function
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const> {
    //enum { arity = sizeof...(Args) };
    typedef function<ReturnType (Args...)> f_type;
};

// for pointers to member function
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) > {
    typedef function<ReturnType (Args...)> f_type;
};

// for function pointers
template <typename ReturnType, typename... Args>
struct function_traits<ReturnType (*)(Args...)>  {
  typedef function<ReturnType (Args...)> f_type;
};

template <typename L> 
typename function_traits<L>::f_type make_function(L l){
  return (typename function_traits<L>::f_type)(l);
}

long times10(int i) { return long(i*10); }

struct X {
  double operator () (float f, double d) { return d*f; } 
};

// test code
int main()
{
    auto lambda = [](int i) { return long(i*10); };
    typedef function_traits<decltype(lambda)> traits;
    traits::f_type ff = lambda;

    cout << make_function([](int i) { return long(i*10); })(2) << ", " << make_function(times10)(2) << ", " << ff(2) << endl;
    cout << make_function(X{})(2,3.0) << endl;

    return 0;
}
Community
  • 1
  • 1
thor
  • 21,418
  • 31
  • 87
  • 173
4

In C++1y there are generic lambdas, and no single call signature (operator()() is a template).

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I think you mean C++14. I'm not sure if the new `operator()()` template will mean that lambdas will be structurally equatable. I may be out of date on this though. – Aluan Haddad Feb 09 '14 at 10:10
  • 2
    @Aluan: There is no "C++14" until the ratification vote takes place. C++1y is the draft for what is expected to become C++14. The final vote could still make some changes. – Ben Voigt Feb 09 '14 at 10:23
1

I was wondering if there is a standard way to get the type signature (i.e. the return type and the types) of its parameters of any given lambda?

No, there isn't.

The reason I ask is that I've always wondered what exactly is the type auto in the declaration like auto l =[](int x,int y)->int{return x+y;}.

It's an unspecified class type, created by the implementation. The whole point of lambdas is they are "anonymous functions" i.e. you do not know their type.

If you want a known type then write a function object type.

In other use cases of auto, it's a convenience and shorter alternative for a longer type name. But for lambdas, is there even an alternative way to declare the lambda variable?

No.

If you want to declare the type yourself then don't use a lambda expression.

My understanding is that a standard lambda is nothing more than a function object, and it is its own type. So, even if two lambdas have the same return type and parameter types, they are still two different, unrelated classes/functors.

Correct. Each lamda expression generates a unique type.

But this there a way to capture the fact that they are the same in terms of type signature?

No, there is no language feature to allow that.

I think the type signature I am looking for can be something like a std::function<> object of the correct types.

Even if it was possible in C++11, it would not help in C++14 where lambda expressions can take any number and any type of argument, e.g. [](auto... a) { }

And anyway, if you don't know the call signature of your lambda function then I would say you are using lambdas wrong. When you write the lambda you should know what its properties are, so either use it right away or put it in a std::function (or some other type that captures its call signature) as early as possible, when you know its properties. If you're creating lambdas and using them non-locally where you don't know the call signature, you're doing it wrong.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • "No, there is no language feature to allow that." which is equivalent to say that, at least the polymorphic lambdas from C++1y, can't be templated, I'm not sure this is correct, I think I have seen templated lambdas before in relation to new C++1y features for polymorphic lambdas. – user2485710 Feb 09 '14 at 22:13
  • "No, there is no language feature to allow that" But in the example from https://en.cppreference.com/w/cpp/utility/functional/function/target_type we see the type of lambda function coded as a name when incapsulated in the std::function. Assuming such a name being unique, it seems to be a way to see if 2 types are the same. Is it right ? – ZviDan Jun 15 '21 at 18:34
  • @ZviDan, no. Because I can do `std::function f( [](long) { return true; } );` where the actual call signature of the lambda is `bool(long)` but I'm stored it in a `function`. I could also do `function f2( [](auto...) { return 11; } )` which stores a completely different lambda in the same type of `std::function`. OP wants to find out the *actual* call signature of the lambda, not the call signature of a `std::function` which might be able to store the lambda, but isn't necessarily the same. – Jonathan Wakely Jun 16 '21 at 19:44
  • @ZviDan and also, to store it in a `std::function` with a similar call signature, you have to already know _a priori_ what the lambda's call signature is (or at least, that it's compatible with the `std::function` type). But if you already know it, then you don't need to find it out (which is what the OP was asking for). – Jonathan Wakely Jun 16 '21 at 19:49
  • @JonathanWakely OK, but can you explain the sense of the fact, if 2 (different) lambdas has the same type name in the sense of example in https://en.cppreference.com/w/cpp/utility/functional/function/target_type ? What does such equivalence (if it happens) mean, in what sense 2 lambdas are similar in this case ? – ZviDan Jun 17 '21 at 06:43
  • They don't have the same type name, that's not what the example is showing. – Jonathan Wakely Jun 18 '21 at 07:14