为什么有时不需要在 lambda 中捕获 const 变量?

2022-01-23 00:00:00 lambda language-lawyer constants c++

考虑以下示例:

#include <cstdlib>

int main() {
    const int m = 42;
    [] { m; }(); // OK

    const int n = std::rand();
    [] { n; }(); // error: 'n' is not captured
}

为什么我需要在第二个 lambda 中捕获 n 而不是在第一个 lambda 中捕获 m?我检查了 C++14 标准中的第 5.1.2 节(Lambda 表达式),但找不到原因.你能指出一个解释这一点的段落吗?

Why do I need to capture n in the second lambda but not m in the first lambda? I checked section 5.1.2 (Lambda expressions) in the C++14 standard but I was unable to find a reason. Can you point me to a paragraph in which this is explained?

更新:我在 GCC 6.3.1 和 7(主干)中都观察到了这种行为.Clang 4.0 和 5(主干)在两种情况下都失败并出现错误(无法在未指定捕获默认值的 lambda 中隐式捕获变量m").

Update: I observed this behavior with both GCC 6.3.1 and 7 (trunk). Clang 4.0 and 5 (trunk) fails with an error in both cases (variable 'm' cannot be implicitly captured in a lambda with no capture-default specified).

推荐答案

对于块作用域的 lambda,在到达作用域中满足特定条件的变量可以在 lambda 内以有限的方式使用,甚至如果他们没有被捕获.

For a lambda at block scope, variables meeting certain criteria in the reaching scope may be used in limited ways inside the lambda, even if they are not captured.

粗略地说,到达作用域包括包含 lambda 的函数的任何局部变量,该变量将在定义 lambda 时的作用域内.所以这包括上面例子中的mn.

Roughly speaking, reaching scope includes any variable local to the function containing the lambda, that would be in scope at the point the lambda was defined. So this includes m and n in the above examples.

某些标准"和有限方式"具体是(从 C++14 开始):

The "certain criteria" and "limited ways" are specifically (as of C++14):

  • 在 lambda 内部,变量不能是 odr-used,这意味着它不能进行任何操作,除了:
    • 显示为废弃值表达式(m; 是其中之一),或
    • 检索其值.
    • Inside the lambda, the variable must not be odr-used, which means it must not undergo any operation except for:
      • appearing as a discarded-value expression (m; is one of these), or
      • having its value retrieved.
      • 一个 const、非volatile 整数或枚举,其初始值设定项为常量表达式,或
      • 一个constexpr,非volatile变量(或此类的子对象)
      • A const, non-volatile integer or enum whose initializer was a constant expression, or
      • A constexpr, non-volatile variable (or a sub-object of such)

      对 C++14 的引用:[expr.const]/2.7、[basic.def.odr]/3(第一句)、[expr.prim.lambda]/12、[expr.prim.lambda]/10.

      References to C++14: [expr.const]/2.7, [basic.def.odr]/3 (first sentence), [expr.prim.lambda]/12, [expr.prim.lambda]/10.

      正如其他评论/答案所建议的,这些规则的基本原理是编译器需要能够合成"一个非捕获 lambda 作为独立于块的自由函数(因为这些东西可以转换为指向函数的指针);如果它知道变量总是具有相同的值,它可以尽管引用变量来执行此操作,或者它可以重复该过程以获得独立于上下文的变量的值.但如果变量有时可能不同,或者例如需要变量的地址,它就不能这样做.

      The rationale for these rules, as suggested by other comments/answers, is that the compiler needs to be able to "synthesize" a no-capture lambda as a free function independent of the block (since such things can be converted to a pointer-to-function); it can do this despite referring to the variable if it knows that the variable would always have the same value, or it can repeat the procedure for obtaining the variable's value independent of the context. But it can't do this if the variable could differ from time to time, or if the variable's address is needed for example.

      在您的代码中,n 由非常量表达式初始化.因此 n 不能在没有被捕获的情况下在 lambda 中使用.

      In your code, n was initialized by a non-constant expression. Therefore n cannot be used in a lambda without being captured.

      m 由常量表达式 42 初始化,因此它确实符合某些标准".丢弃值表达式不使用该表达式,因此可以使用 m; 而不捕获 m.gcc 是正确的.

      m was initialized by a constant expression 42, so it does meet the "certain criteria". A discarded-value expression does not odr-use the expression, so m; can be used without m being captured. gcc is correct.

      我会说这两个编译器之间的区别在于clang认为m;是odr-use m,但gcc没有.[basic.def.odr]/3 的第一句相当复杂:

      I would say that the difference between the two compilers is that clang considers m; to odr-use m, but gcc does not. The first sentence of [basic.def.odr]/3 is quite complicated:

      一个变量 x,其名称显示为一个潜在的评估表达式 ex 是 odr-used 由 ex除非将左值到右值的转换应用于 x 会产生一个不会调用任何非平凡函数的常量表达式,并且如果 x 是一个对象,则 ex 是表达式 e 的潜在结果集合中的一个元素,其中左值到右值转换应用于 ee 是丢弃值表达式.

      A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion is applied to e, or e is a discarded-value expression.

      但仔细阅读后,它确实特别提到了丢弃值表达式不odr-use该表达式.

      but upon reading closely it does specifically mention that a discarded-value expression does not odr-use the expression.

      C++11 版本的 [basic.def.odr] 原本不包括丢弃值表达式的情况,因此在已发布的 C++11 下,clang 的行为是正确的.然而,出现在 C++14 中的文本被认为是针对 C++11 的缺陷(问题 712),因此编译器即使在 C++11 模式下也应更新其行为.

      C++11's version of [basic.def.odr] originally did not include the discarded-value expression case, so clang's behaviour would be correct under the published C++11. However the text that appears in C++14 was accepted as a Defect against C++11 (Issue 712), so compilers should update their behaviour even in C++11 mode.

相关文章