Does the compiler generate a different type for each lambda?

醉酒当歌 提交于 2019-12-10 10:16:22

问题


Disclaimer: Do not use the code in this question. It invokes undefined behaviour. The core statement of the question, whether the compiler generates a new type for each lambda, and the corresponding answer remain valid.

To take get a function pointer to a lambda with a capture 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 assign the lambda with a capture (which might be a function parameter) to a static variable inside the function, so I do not have to capture it when using it in the other lambda. The other captureless lambda can then happily decay to a function pointer which I can pass to some shared library.

Now one could attempt to generalize this:

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

However, as proxy is a static variable it will be overwritten on each call to the function. Because it is a template, I think that it will be overwritten only when I call the same instantiation, i.e. each instantiation has it's own static proxy.

Nevertheless, 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) one might expect the same types to be deduced. However, lambdas are compiler generated structs and it seems as if the compiler would generate a different type for each lambda, regardless of whether previous lambdas look like they could be reused.

So the above trick would be extremely useful for me under the condition that the compiler will definitely generate a different type for each lambda. If this is not the case the generalisation of my hack is useless.


回答1:


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

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




回答2:


What you've effectively done is created a singleton holding a copy of the lambda, distinct for each type of lambda (which per [expr.prim.lambda.closure]/1 means every lambda expression). This works generally because lambdas are immutable, however, a mutable lambda would reveal 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

The reason for this is an incorrect assumption in your original write-up:

However, as proxy is a static variable it will be overwritten on each call to the function.

That's not true. The static variable is set once, the first time the function is called. Further calls to the same function will not evaluate the statement, so proxy will retain the value from the first time that particular template instantiation of get_ptr is called.

Normally you could separate the static declaration and assignment, but in this case you can't because lambdas don't have a default constructor (yet). If you did that, then calling get_ptr a second time on a lambda would reset the lambda that the first call's returned pointer was using, changing the above result to have:

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


来源:https://stackoverflow.com/questions/43970960/does-the-compiler-generate-a-different-type-for-each-lambda

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!