Consider the following example:
#include <cstdlib> int main() { const int m = 42; [] { m; }(); // OK const int n = std::rand(); [] { n; }(); // error: 'n' is not captured }
Why do I need to capture n
in the second lambda but not m
in the first lambda? I checked section 5.1.2 (Lambda expressions) in the C++14 standard but I was unable to find a reason. Can you point me to a paragraph in which this is explained?
Update: I observed this behavior with both GCC 6.3.1 and 7 (trunk). Clang 4.0 and 5 (trunk) fails with an error in both cases (variable 'm' cannot be implicitly captured in a lambda with no capture-default specified
).
By default, variables are captured by const value . This means when the lambda is created, the lambda captures a constant copy of the outer scope variable, which means that the lambda is not allowed to modify them.
The lambda is capturing an outside variable. A lambda is a syntax for creating a class. Capturing a variable means that variable is passed to the constructor for that class. A lambda can specify whether it's passed by reference or by value.
When a lambda object outlives one of its reference-captured objects, execution of the lambda object's function call operator results in undefined behavior once that reference-captured object is accessed. Therefore, a lambda object must not outlive any of its reference-captured objects.
Local variables from outer scope can be captured inside Lambda in 2 modes i.e.
For a lambda at block scope, variables meeting certain criteria in the reaching scope may be used in limited ways inside the lambda, even if they are not captured.
Roughly speaking, reaching scope includes any variable local to the function containing the lambda, that would be in scope at the point the lambda was defined. So this includes m
and n
in the above examples.
The "certain criteria" and "limited ways" are specifically (as of C++14):
m;
is one of these), orconst
, non-volatile
integer or enum whose initializer was a constant expression, orconstexpr
, non-volatile
variable (or a sub-object of such)References to C++14: [expr.const]/2.7, [basic.def.odr]/3 (first sentence), [expr.prim.lambda]/12, [expr.prim.lambda]/10.
The rationale for these rules, as suggested by other comments/answers, is that the compiler needs to be able to "synthesize" a no-capture lambda as a free function independent of the block (since such things can be converted to a pointer-to-function); it can do this despite referring to the variable if it knows that the variable would always have the same value, or it can repeat the procedure for obtaining the variable's value independent of the context. But it can't do this if the variable could differ from time to time, or if the variable's address is needed for example.
In your code, n
was initialized by a non-constant expression. Therefore n
cannot be used in a lambda without being captured.
m
was initialized by a constant expression 42
, so it does meet the "certain criteria". A discarded-value expression does not odr-use the expression, so m;
can be used without m
being captured. gcc is correct.
I would say that the difference between the two compilers is that clang considers m;
to odr-use m
, but gcc does not. The first sentence of [basic.def.odr]/3 is quite complicated:
A variable
x
whose name appears as a potentially-evaluated expressionex
is odr-used byex
unless applying the lvalue-to-rvalue conversion tox
yields a constant expression that does not invoke any non-trivial functions and, ifx
is an object,ex
is an element of the set of potential results of an expressione
, where either the lvalue-to-rvalue conversion is applied toe
, ore
is a discarded-value expression.
but upon reading closely it does specifically mention that a discarded-value expression does not odr-use the expression.
C++11's version of [basic.def.odr] originally did not include the discarded-value expression case, so clang's behaviour would be correct under the published C++11. However the text that appears in C++14 was accepted as a Defect against C++11 (Issue 712), so compilers should update their behaviour even in C++11 mode.
Its because it is a constant expression, the compiler treats is as if it were [] { 42; }();
The rule in [expr.prim.lambda] is:
If a lambda-expression or an instantiation of the function call operator template of a generic lambda odr-uses (3.2) this or a variable with automatic storage duration from its reaching scope, that entity shall be captured by the lambda-expression.
Here a quote from the standard [basic.def.odr]:
A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless applying the lvalue-to-rvalue conversion to x yields a constant expression (...) or e is a discarded-value expression.
(Removed not so important part to keep it short)
My simple understanding is: the compiler knows that m
is constant at compile-time, whereas n
will change at run-time and therefore n
has to be captured. n
would be odr-used, because you have to actually take a look at what is inside n
at run time. In other words the fact that "there can be only one" definition of n
is relevant.
This is from a comment by M.M:
m is a constant expression because it's a const automatic variable with constant expression initializer, but n is not a constant expression because its initializer was not a constant expression. This is covered in [expr.const]/2.7. The constant expression is not ODR-used, according to first sentence of [basic.def.odr]/3
See here for a demo.
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