在嵌套 lambda 的情况下如何初始化 lambda 捕获?



好的,所以这是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,特别是bm2打印的值。以下是我的理解/解释:

std::cout << a << b << c;m2中执行时,根据 [expr.prim.lambda]p16(强调我的(:

如果 lambda 表达式 m2 捕获了一个实体,并且该实体被立即封闭的 lambda 表达式捕获 M1,则 M2 的捕获转换如下:

— 如果 M1通过复制捕获实体,则 M2 捕获 M1 闭包类型的相应非静态数据成员;

因此,m2内部的a应捕获到相应a中捕获的闭合类型m1。由于am1通过复制捕获,而am2中也通过复制捕获,因此am2中的值应该1

标准继续说(再次强调我的(:

— 如果 M1通过引用捕获实体,则 M2 捕获 M1 捕获的同一实体

我相信这里的">同一实体"是指m1通过引用捕获的实体,当被m2捕获时,它应该是 - 如果它是通过引用捕获的,则是指对同一实体的引用,如果是副本捕获,则为对同一实体的副本。

因此,对于m2中的b,应指在两个 lambda 之外定义的bm2b的值应该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捕获的同一实体。

是的,所以bm2捕获列表中捕获的不是引用本身(即m1的捕获(,而是它指向的对象。

但是m2是按值还是按引用捕获b完全取决于m2捕获列表中所写的内容。在b之前没有&,所以b是按值捕获的。

m2内部的b何时初始化?

当控制达到auto m2 = ...;.此时,将检查对存储在m1中的b的引用,并将其指向的对象复制到m2中。


这里有一个更简单的解释。

  • 按值捕获引用时,将创建它指向的对象的副本。

  • 通过引用捕获引用时,将创建对它指向的对象的引用。

在这里,"捕获引用"同样适用于捕获实际引用,以及捕获封装 lambda 的引用捕获。

最新更新