Okay, so this is right off the bat from [expr.prim.lambda]p16 in n3337.pdf. Below code is given as an example:
int a = 1, b = 1, c = 1;
auto m1 = [a, &b, &c]() mutable
{
auto m2 = [a, b, &c]() mutable
{
std::cout << a << b << c; // Shouldn't this print 113 or 133?
a = 4; b = 4; c = 4;
};
a = 3; b = 3; c = 3;
m2();
};
a = 2; b = 2; c = 2;
m1();
std::cout << a << b << c; // Okay, this prints 234
and that it shall generate below output:
123234
However, the way I have understood the text in [expr.prim.lambda] (which is somehow obviously flawed), I feel the output should be 113234
, specifically the value of b
printed in m2
. Below is my understanding/explanation:
When std::cout << a << b << c;
is executed inside m2
, as per [expr.prim.lambda]p16 (emphasis mine):
If a lambda-expression m2 captures an entity and that entity is captured by an immediately enclosing lambda expression m1, then m2’s capture is transformed as follows:
— if m1 captures the entity by copy, m2 captures the corresponding non-static data member of m1’s closure type;
Therefore, the a
inside m2
shall capture the member generated to the corresponding a
captured in the closure type m1
. Since a
in m1
captures by copy, and a
in m2
also captures by copy, a
's value in m2
should be 1
.
The standard goes on to say (again, emphasis mine):
— if m1 captures the entity by reference, m2 captures the same entity captured by m1.
I believe "same entity" here refers to the entity captured by m1
via reference, and when captured by m2
it shall be - a reference to the same entity if it's a capture by reference, or a copy of it if it's a capture by copy.
Therefore for b
in m2
shall refer to the b
defined outside both lambdas. The value of b
in m2
then should be 1
as b
is also captured by copy.
Where am I going wrong? More specifically, when is b
inside m2
initialised?
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.
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 service stores your function code in an internal S3 bucket that's private to your account. Each AWS account is allocated 75 GB of storage in each Region. Code storage includes the total storage used by both Lambda functions and layers.
Capture clauseA lambda can introduce new variables in its body (in C++14), and it can also access, or capture, variables from the surrounding scope. A lambda begins with the capture clause. It specifies which variables are captured, and whether the capture is by value or by reference.
First, note that whether a capture is by copy or by reference depends only on the lambda expression's own lambda-introducer (the initial []
part), per C++11 [expr.prim.lambda] paragraph 14 (or C++17 [expr.prim.lambda.capture] paragraph 10).
The pieces you quoted from C++11 [expr.prim.lambda]/16 (or the same in C++17 [expr.prim.lambda.capture]/13) change only what entity is captured, not the type of the capture. So in the example, the inner lambda used to initialize m2
captures the b
from the original definition, by copy.
Then, note C++11 [expr.prim.lambda]/21:
When the lambda-expression is evaluated, the entities that are captured by copy are used to direct-initialize each corresponding non-static data member of the resulting closure object.
(C++17 [expr.prim.lambda.capture]/15 starts out the same, but additional wording is added for the init-capture syntax like [var=init]
.)
In the example, the inner lambda-expression for initializing m2
is evaluated, and the closure object's member for b
is initialized, each time m1.operator()
is invoked, not in the order the lambda-expression appears in the code. Since the lambda for m2
captures the original b
by copy, it gets the value of that b
at the time m1
is called. If m1
were called multiple times, that initial value for b
could be different each time.
— if
m1
captures the entity by reference,m2
captures the same entity captured bym1
.
Yes, so b
in m2
's capture list captures not the reference itself (the capture of m1
, that is), but the object that it points to.
But whether m2
captures b
by value or by reference is determined solely by what's written in m2
's capture list. There's no &
before b
, so b
is captured by value.
when is
b
insidem2
initialised?
When control reaches auto m2 = ...;
. At that point, the reference to b
stored in m1
is examined, and the object it points to is copied into m2
.
Here's an easier explanation.
When you capture a reference by value, you make a copy of the object that it points to.
When you capture a reference by reference, you make a reference to the object that it points to.
Here, "capturing a reference" applies equally well to capturing actual references, and to capturing reference-captures of enclosing lambdas.
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