好的,所以这是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
,特别是b
m2
打印的值。以下是我的理解/解释:
当std::cout << a << b << c;
在m2
中执行时,根据 [expr.prim.lambda]p16(强调我的(:
如果 lambda 表达式 m2 捕获了一个实体,并且该实体被立即封闭的 lambda 表达式捕获 M1,则 M2 的捕获转换如下:
— 如果 M1通过复制捕获实体,则 M2 捕获 M1 闭包类型的相应非静态数据成员;
因此,m2
内部的a
应捕获到相应a
中捕获的闭合类型m1
。由于a
m1
通过复制捕获,而a
m2
中也通过复制捕获,因此a
在m2
中的值应该1
。
标准继续说(再次强调我的(:
— 如果 M1通过引用捕获实体,则 M2 捕获 M1 捕获的同一实体。
我相信这里的">同一实体"是指m1
通过引用捕获的实体,当被m2
捕获时,它应该是 - 如果它是通过引用捕获的,则是指对同一实体的引用,如果是副本捕获,则为对同一实体的副本。
因此,对于m2
中的b
,应指在两个 lambda 之外定义的b
。m2
中b
的值应该1
,因为b
也被复制捕获。
我哪里出错了?更具体地说,b
内部m2
何时初始化?
首先,请注意,捕获是通过复制还是通过引用仅取决于 lambda 表达式自己的 lambda 引入器(初始[]
部分(,根据 C++11 [expr.prim.lambda] 第 14 段(或 C++17 [expr.prim.lambda.capture] 第 10 段(。
您从 C++11 [expr.prim.lambda]/16(或 C++17 [expr.prim.lambda.capture]/13 中引用的片段仅更改捕获的实体,而不是捕获的类型。因此,在示例中,用于初始化m2
的内部 lambda 通过复制从原始定义中捕获b
。
然后,注意 C++11 [expr.prim.lambda]/21:
计算lambda 表达式时,copy 捕获的实体用于直接初始化生成的闭包对象的每个相应的非静态数据成员。
(C++17 [expr.prim.lambda.capture]/15 的开头相同,但为init-capture语法添加了额外的措辞,如[var=init]
.(
在该示例中,每次调用m1.operator()
时,都会计算用于初始化m2
的内部lambda表达式,并初始化用于b
的闭包对象的成员,而不是按照lambda 表达式在代码中出现的顺序。由于m2
的 lambda 通过复制捕获原始b
,因此它会在调用m1
时获取该b
的值。如果多次调用m1
,则每次b
的初始值可能不同。
— 如果
m1
通过引用捕获实体,则m2
捕获m1
捕获的同一实体。
是的,所以b
m2
捕获列表中捕获的不是引用本身(即m1
的捕获(,而是它指向的对象。
但是m2
是按值还是按引用捕获b
完全取决于m2
捕获列表中所写的内容。在b
之前没有&
,所以b
是按值捕获的。
m2
内部的b
何时初始化?
当控制达到auto m2 = ...;
.此时,将检查对存储在m1
中的b
的引用,并将其指向的对象复制到m2
中。
这里有一个更简单的解释。
-
按值捕获引用时,将创建它指向的对象的副本。
-
通过引用捕获引用时,将创建对它指向的对象的引用。
在这里,"捕获引用"同样适用于捕获实际引用,以及捕获封装 lambda 的引用捕获。