At an intuitive level, it makes sense that a lambda that needs to carry no state (through a reference or otherwise) should be cleanly convertible to a naked function pointer. However, I was recently surprised to see the following failing in GCC, Clang, and MSVC:
int main(int, char *[]) { void (*fp)() = []{}; // OK //fp = [=]{}; // XXX - no user defined conversion operator available //fp = [&]{}; // XXX - same ... }
The C++17 spec (or at least visible public draft version N4713), refers in item 7 of § 8.4.5.1 [expr.prim.lambda.closure] to lambdas with and without captures:
The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage (10.5) having the same parameter and return types as the closure type’s function call operator. ...
However, looking into the formal grammar you can see the following in § 8.4.5 [expr.prim.lambda]:
- lambda-expression :
- lambda-introducer compound-statement
- ...
- lambda-introducer :
- [ lambda-captureopt ]
- ...
and in § 8.4.5.2 [expr.prim.lambda.capture]:
- lambda-capture :
- capture-default
- capture-list
- capture-default, capture-list
- capture-default :
- &
- =
So all the compilers were actually obeying the letter of the law to my dismay...
Why does the language define the existence of a capture as a narrow grammatical distinction in the declaration instead of basing it on whether the body contains references to any non-static/captured state?
Method definition: Lambda body. A capture clause of lambda definition is used to specify which variables are captured and whether they are captured by reference or by value. An empty capture closure [ ], indicates that no variables are used by lambda which means it can only access variables that are local to it.
A lambda expression can implement a functional interface by defining an anonymous function that can be passed as an argument to some method.
One of the new features introduced in Modern C++ starting from C++11 is Lambda Expression. It is a convenient way to define an anonymous function object or functor. It is convenient because we can define it locally where we want to call it or pass it to a function as an argument.
A capture is a binding to a free variable in the lambda form. They turn the lambda expression into a closed form (a closure), with no free variables. Consider: auto f1 = [](int a, int x, int y){ return a * x + y; }; int x = 40, y = 2; auto f2 = [x, y](int a){ return a * x + y; };
The change that allowed the conversion was initiated by a national body comment. See n3052: Converting Lambdas to Function Pointers which refers to national body comment UK 42:
A lambda with an empty capture list has identical semantics to a regular function type. By requiring this mapping we get an efficient lambda type with a known API that is also compatible with existing operating system and C library functions.
and the resolution from N3052
was:
Resolution: Add a new paragraph: "A lambda expression with an empty capture set shall be convertible to pointer to function type R(P), where R is the return type and P is the parameter-type-list of the lambda expression." Additionally it might be good to (a) allow conversion to function reference and (b) allow extern "C" function pointer types.
...
Add a new paragraph after paragraph 5. The intent of this edit is to obtain a closure-to-function-pointer conversion for a lambda with no lambda-capture.
The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type's function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.
and that is where we are today. Note the comment said empty capture list and what we have today seems to match the intent as worded in the comment.
It looks like it was a fix based on a national body comment and was applied narrowly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With