在嵌套 lambda 的情况下如何初始化 lambda 捕获?
How are lambda captures initialized in case of nested lambdas?
好的,所以这是从 n3337.pdf 中的 [expr.prim.lambda]p16 开始的。以下代码作为示例给出:
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
并且它将生成以下输出:
123234
然而,根据我对 [expr.prim.lambda] 中文本的理解方式(这在某种程度上明显有缺陷),我觉得输出应该是 113234
,特别是 [=12= 的值] 在 m2
中打印。下面是我的 understanding/explanation:
当 std::cout << a << b << c;
在 m2
内执行时,根据 [expr.prim.lambda]p16(强调我的):
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;
因此m2
里面的a
应该捕获到闭包类型m1
中捕获的对应a
生成的成员。由于m1
中的a
是复制捕获,而m2
中的a
也是复制捕获,所以a
在m2
中的值应该是1
.
标准继续说(再次强调我的):
— if m1 captures the entity by reference, m2 captures the same entity captured by m1.
我相信这里的“同一个实体”指的是通过引用被m1
捕获的实体,当被m2
捕获时它应该是-a如果是引用捕获,则引用同一实体;如果是复制捕获,则引用同一实体的副本。
因此 m2
中的 b
应引用两个 lambda 表达式之外定义的 b
。 m2
中的 b
的值应该是 1
因为 b
也被复制捕获。
我哪里错了?更具体地说,m2
中的 b
何时初始化?
— if m1
captures the entity by reference, m2
captures the same entity captured by m1
.
是的,所以 m2
的捕获列表中的 b
捕获的不是引用本身(即捕获 m1
),而是它指向的对象。
但是 m2
是按值还是按引用捕获 b
完全取决于 m2
的捕获列表中所写的内容。 b
之前没有&
,所以b
被值捕获了。
when is b
inside m2
initialised?
当控制达到auto m2 = ...;
时。此时,将检查存储在 m1
中的对 b
的引用,并将其指向的对象复制到 m2
.
这里有一个更简单的解释。
按值捕获引用时,会复制它指向的对象。
当您通过引用捕获引用时,您引用了它指向的对象。
在这里,"capturing a reference" 同样适用于捕获实际引用,以及捕获封闭 lambda 的引用捕获。
首先,请注意捕获是通过复制还是通过引用仅取决于 lambda 表达式自身的 lambda-introducer(初始 []
部分),每C++11 [expr.prim.lambda] 第 14 段(或 C++17 [expr.prim.lambda.capture] paragraph 10)。
您从 C++11 [expr.prim.lambda]/16(或 C++17 [expr.prim.lambda.capture]/13 中引用的内容)仅更改捕获的实体,而不是捕获的类型。因此在示例中,用于初始化 m2
的内部 lambda 通过复制从原始定义中捕获 b
。
然后,注意 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 开始时相同,但为 init-capture 语法添加了额外的措辞,如 [var=init]
。)
在这个例子中,内部 lambda-expression 用于初始化 m2
被评估,闭包对象的成员 b
被初始化,每次m1.operator()
被调用,而不是按照 lambda-expression 在代码中出现的顺序。由于 m2
的 lambda 通过副本捕获原始 b
,因此它在调用 m1
时获得 b
的值。如果多次调用 m1
,则 b
的初始值每次都可能不同。
好的,所以这是从 n3337.pdf 中的 [expr.prim.lambda]p16 开始的。以下代码作为示例给出:
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
并且它将生成以下输出:
123234
然而,根据我对 [expr.prim.lambda] 中文本的理解方式(这在某种程度上明显有缺陷),我觉得输出应该是 113234
,特别是 [=12= 的值] 在 m2
中打印。下面是我的 understanding/explanation:
当 std::cout << a << b << c;
在 m2
内执行时,根据 [expr.prim.lambda]p16(强调我的):
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;
因此m2
里面的a
应该捕获到闭包类型m1
中捕获的对应a
生成的成员。由于m1
中的a
是复制捕获,而m2
中的a
也是复制捕获,所以a
在m2
中的值应该是1
.
标准继续说(再次强调我的):
— if m1 captures the entity by reference, m2 captures the same entity captured by m1.
我相信这里的“同一个实体”指的是通过引用被m1
捕获的实体,当被m2
捕获时它应该是-a如果是引用捕获,则引用同一实体;如果是复制捕获,则引用同一实体的副本。
因此 m2
中的 b
应引用两个 lambda 表达式之外定义的 b
。 m2
中的 b
的值应该是 1
因为 b
也被复制捕获。
我哪里错了?更具体地说,m2
中的 b
何时初始化?
— if
m1
captures the entity by reference,m2
captures the same entity captured bym1
.
是的,所以 m2
的捕获列表中的 b
捕获的不是引用本身(即捕获 m1
),而是它指向的对象。
但是 m2
是按值还是按引用捕获 b
完全取决于 m2
的捕获列表中所写的内容。 b
之前没有&
,所以b
被值捕获了。
when is
b
insidem2
initialised?
当控制达到auto m2 = ...;
时。此时,将检查存储在 m1
中的对 b
的引用,并将其指向的对象复制到 m2
.
这里有一个更简单的解释。
按值捕获引用时,会复制它指向的对象。
当您通过引用捕获引用时,您引用了它指向的对象。
在这里,"capturing a reference" 同样适用于捕获实际引用,以及捕获封闭 lambda 的引用捕获。
首先,请注意捕获是通过复制还是通过引用仅取决于 lambda 表达式自身的 lambda-introducer(初始 []
部分),每C++11 [expr.prim.lambda] 第 14 段(或 C++17 [expr.prim.lambda.capture] paragraph 10)。
您从 C++11 [expr.prim.lambda]/16(或 C++17 [expr.prim.lambda.capture]/13 中引用的内容)仅更改捕获的实体,而不是捕获的类型。因此在示例中,用于初始化 m2
的内部 lambda 通过复制从原始定义中捕获 b
。
然后,注意 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 开始时相同,但为 init-capture 语法添加了额外的措辞,如 [var=init]
。)
在这个例子中,内部 lambda-expression 用于初始化 m2
被评估,闭包对象的成员 b
被初始化,每次m1.operator()
被调用,而不是按照 lambda-expression 在代码中出现的顺序。由于 m2
的 lambda 通过副本捕获原始 b
,因此它在调用 m1
时获得 b
的值。如果多次调用 m1
,则 b
的初始值每次都可能不同。