Does the compiler design a different type for each lambda?

Disclaimer: Do not use code in this question. It invokes undefined behavior. The basic question is whether the compiler generates a new type for each lambda, and the corresponding answer is still valid.

To get a lambda pointer function with capturing I came up with the following trick:

auto f = [&a] (double x) { return a*x; };
static auto proxy = f;
double(*ptr)(double) = [] (double x) { return proxy(x); };
// do something with ptr

      

I am assigning a capturing lambda (which could be a function parameter) to a static variable inside a function, so I don't need to capture it when using it in another lambda. Then another light-hearted lambda can happily decay into a function pointer that I can pass into some kind of shared library.

Now we can try to generalize this:

template < typename F >
decltype(auto) get_ptr(F f)
{
  static auto proxy = f;
  return [] (auto ... args) { return proxy(args...); };
}

      

However, since it proxy

is a static variable, it will be overwritten every time the function is called. Since this is a template, I think it will only be overwritten when I call the same instance, i.e. Each instance has its own static one proxy

.

However, the following works:

#include <cassert>

template < typename F >
decltype(auto) get_ptr(F f)
{
  static auto proxy = f;
  return [] (auto ... args) { return proxy(args...); };
}

int main()
{
  auto f1 = [ ](double,double) { return 1; };
  auto f2 = [=](double,double) { return 2; };
  auto f3 = [&](double,double) { return 3; };
  auto f4 = [&](double,double) { return 4; };
  auto f5 = [ ](double,double) { return 5; };

  int(*p1)(double,double) = get_ptr(f1);
  int(*p2)(double,double) = get_ptr(f2);
  int(*p3)(double,double) = get_ptr(f3);
  int(*p4)(double,double) = get_ptr(f4);
  int(*p5)(double,double) = get_ptr(f5);

  assert( p1(0,0) == 1 );
  assert( p2(0,0) == 2 );
  assert( p3(0,0) == 3 );
  assert( p4(0,0) == 4 );
  assert( p5(0,0) == 5 );
}

      

This looks suspicious as for get_ptr(f1)

and get_ptr(f5)

the same types can be expected to be inferred. However, lambdas are compiler-generated structures, and it looks like the compiler created a different type for each lambda, regardless of whether the previous lambdas look like they can be reused.

So the above trick would be very helpful to me, provided that the compiler would definitely create a different type for each lambda. If it is not, then generalizing my hack is useless.

+3


source to share


2 answers


From the project spec (specifically for n3376), 5.1.2.3 (emphasis mine).



The type of a lambda expression (which is also the type of a closure object) is a unique , unnamed type of a non-unit class - called a closure type ...

+5


source


What you've done efficiently is created by a singleton containing a copy of the lambda that is different for each lambda type (what [expr.prim. Lambda.closure] / 1 means each lambda expression). This works mainly because lambdas are immutable, however a volatile lambda shows the difference:

auto f6 = [i = 1]( double, double ) mutable { return 6 * i++; };
int(*p6)(double,double) = get_ptr(f6);

assert( p6(0,0) == 6 );
assert( p6(0,0) == 12 );

int(*p6_prime)(double,double) = get_ptr(f6);
assert( p6_prime(0,0) == 18 ); // (!) p6_prime === p6
assert( p6(0,0) == 24 );

assert( f6(0,0) == 6 );
assert( f6(0,0) == 12 );
assert( f6(0,0) == 18 );
assert( f6(0,0) == 24 );

      

Live demo on wandbox

This is due to a wrong assumption in your original post:



However, since the proxy is a static variable, it will be overwritten each time the function is called.

This is not true. The variable is static

set once, the first time the function is called. Further calls to the same function will not evaluate the operator, so it will proxy

retain the value from the first time a specific instantiation is get_ptr

called.

You can usually separate the declaration and assignment static

, but in this case you cannot, because lambdas does not have a default constructor (yet). If you did this, then when you call it get_ptr

a second time on the lambda, it will reset the lambda used by the first pointer returned by the call, changing the result:

int(*p6_prime)(double,double) = get_ptr(f6);
assert( p6_prime(0,0) == 6 );
assert( p6(0,0) == 12 ); // (!) p6 === p_prime

      

+2


source







All Articles