这个带有空捕获列表的lambda如何能引用到外层作用域的名称?

7
在 C++14 标准的 §5.1.2/12 中,有一个 lambda 表达式的例子,似乎可以引用到外部作用域的变量 x,尽管:

  1. 捕获列表是空的,即没有捕获默认值
  2. 注释说它“不会捕获 x

以下是示例:

void f(int, const int (&)[2] = {}) { } // #1
void test() {
  const int x = 17;
  auto g = [](auto a) {
    f(x); // OK: calls #1, does not capture x
  };
}

请看它可以编译。这似乎取决于xconst;如果删除const,则由于捕获列表为空而不再编译。即使我使参数成为int,使其不再是通用lambda,它仍然会发生。

即使捕获列表为空,lambda如何引用x?而且在同时显然没有捕获x的情况下,这是如何实现的(正如注释所说)?

关于此主题,我找到的最接近的东西是其他人在评论中间接地注意到了这一点。

以下是标准的完整部分5.1.2/12:

A lambda-expression with an associated capture-default that does not explicitly capture this or a variable with automatic storage duration (this excludes any id-expression that has been found to refer to an init-capture’s associated non-static data member), is said to implicitly capture the entity (i.e., this or a variable) if the compound-statement:

  • odr-uses (3.2) the entity, or
  • names the entity in a potentially-evaluated expression (3.2) where the enclosing full-expression depends on a generic lambda parameter declared within the reaching scope of the lambda-expression.

[ Example:

void f(int, const int (&)[2] = {}) { } // #1
void f(const int&, const int (&)[1]) { } // #2
void test() {
  const int x = 17;
  auto g = [](auto a) {
    f(x); // OK: calls #1, does not capture x
  };

  auto g2 = [=](auto a) {
    int selector[sizeof(a) == 1 ? 1 : 2]{};
    f(x, selector); // OK: is a dependent expression, so captures x
  };
}

end example ] All such implicitly captured entities shall be declared within the reaching scope of the lambda expression. [ Note: The implicit capture of an entity by a nested lambda-expression can cause its implicit capture by the containing lambda-expression (see below). Implicit odr-uses of this can result in implicit capture. —end note ]


1
我认为简短的解释是编译器执行常量折叠,并且所有对“x”的使用都被替换为实际值。常量折叠 - Jesse Good
@JesseGood 谢谢。这是我正在考虑的事情,但我不知道C++中的规则,无法确定。我倾向于相信这是Barry引用的规则的实际效果。 - Jorge Israel Peña
1
基本上,当你使用 & 时,你正在获取它的地址,因此编译器需要为变量分配存储空间。 - Jesse Good
@JesseGood 非常有道理。非常感谢! - Jorge Israel Peña
1个回答

11
您引用的是正确的语句。如果变量需要定义,那么它就需要被捕获,这就是所谓的ODR-use。这意味着该变量在需要定义的上下文中使用,例如获取其地址或者获取对它的引用等等。但基本上有一个关键的例外,根据[basic.def.odr]:

如果一个名称为 ex 的可能求值表达式中出现了变量 x,则应该认为表达式 ex 使用了 x,除非对 x 应用左值到右值转换(4.1)会产生一个不调用任何非平凡函数的常量表达式(5.20),并且如果 x 是对象,则 ex 是表达式 e 的潜在结果集中的一个元素,其中要么将 e 应用到左值到右值转换(4.1),要么 e 是一个弃值表达式(5.2.9)。

在您的示例中,对x应用lvalue-to-rvalue转换会产生一个常量表达式(因为x是一个常量整数),因此它不是odr-used。由于它没有被odr-used,因此不必被捕获。
另一方面,如果x被绑定到引用(例如,f将其参数作为const int&),那么它将被odr-used,因此必须被捕获。在第二个示例中,x的“odr-use-ness”取决于通用lambda参数是什么,因此出于健全性考虑,它被认为已被捕获。

感谢您澄清这个问题!这是在描述启用常数折叠的本质,正如Jesse在我的问题上所评论的那样? - Jorge Israel Peña
基本上,如果它是一个常量,只有在它被用作需要存在为对象的上下文中时,你才需要将其捕获为常量。 - Barry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接