在嵌套 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也是复制捕获,所以am2中的值应该是1.

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

if m1 captures the entity by reference, m2 captures the same entity captured by m1.

我相信这里的“同一个实体”指的是通过引用被m1捕获的实体,当被m2捕获时它应该是-a如果是引用捕获,则引用同一实体;如果是复制捕获,则引用同一实体的副本。

因此 m2 中的 b 应引用两个 lambda 表达式之外定义的 bm2 中的 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 的初始值每次都可能不同。