This compiles:
int main() {
const int x = 123;
auto g = []() { std::cout << x << "\n"; };
g();
}
But this:
int main(){
const float x = 123;
auto g = []() { std::cout << x << "\n"; };
g();
}
produces:
"error: 'x' is not captured"
Why?
I've tested it on both GCC (various versions from 5.0.0 to 8.0.0) and Clang (various versions from 4.0.0 to 6.0.0). It behaves the same in all cases.
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.
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.
Copying a lambda will copy its state.
The mutable keyword is used so that the body of the lambda expression can modify its copies of the external variables x and y , which the lambda expression captures by value. Because the lambda expression captures the original variables x and y by value, their values remain 1 after the lambda executes.
Lambda's scope can implicitly capture variables within its reaching scope.
Your variables are in the reaching scope, since they are local to the (main) function that defines the lambda.
However, there are certain criteria in which variables can be captured via this mechanism, as mentioned in [expr.prim.lambda]/12:
A lambda-expression with an associated capture-default that does not explicitly capture this or a variable with automatic storage duration [..], is said to implicitly capture the entity (i.e., this or a variable) if the compound-statement:
-odr-uses ([basic.def.odr]) the entity, or
-names the entity in a potentially-evaluated expression ([basic.def.odr]) where the enclosing full-expression depends on a generic lambda parameter declared within the reaching scope of the lambda-expression.
The most important part is in [expr.const]/2.7:
A conditional-expression
e
is a core constant expression unless the evaluation ofe
, [..] would evaluate one of the following expressions:an lvalue-to-rvalue conversion ([conv.lval]) unless it is applied to:
a non-volatile glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression.
So const int
is a core constant expression while const float
is not.
Moreover [expr.const]1826 mentions:
A const integer initialized with a constant can be used in constant expressions, but a const floating point variable initialized with a constant cannot.
Read more in Why is a const variable sometimes not required to be captured in a lambda?
C++14 draft N4140 5.1.2.12 [expr.prim.lambda] :
A lambda-expression with an associated capture-default that does not explicitly capture this or a variable with automatic storage duration (this excludes any id-expression that has been found to refer to an init-capture’s associated non-static data member), is said to implicitly capture the entity (i.e., this or a variable) if the compound-statement:
odr-uses (3.2) the entity, or
names the entity in a potentially-evaluated expression (3.2) where the enclosing full-expression depends on a generic lambda parameter declared within the reaching scope of the lambda-expression.
Also, .open-std.org :
A const integer initialized with a constant can be used in constant expressions, but a const floating point variable initialized with a constant cannot. This was intentional, to be compatible with C++03 while encouraging the consistent use of constexpr. Some people have found this distinction to be surprising, however.
It was also observed that allowing const floating point variables as constant expressions would be an ABI-breaking change, since it would affect lambda capture.
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