为什么constvariables有时不需要被lambda捕获?

考虑下面的例子:

#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节( Lambdaexpression式 ),但我无法find一个理由。 你能指出我的解释这一段吗?

更新:我观察到GCC 6.3.1和7(trunk)的行为。 在这两种情况下,Clang 4.0和5(trunk)都会失败并发生错误( variable 'm' cannot be implicitly captured in a lambda with no capture-default specified )。

对于块范围内的lambda,即使未被捕获,在范围内满足特定条件的variables也可以在lambda内以有限的方式使用。

粗略地说, 达到范围包括任何包含lambda函数的局部variables,这将在lambda定义的范围内。 所以这在上面的例子中包括了mn

“特定的标准”和“有限的方式”是特别的(从C ++ 14开始):

  • 在lambda里面,variables不能被使用 ,这意味着它不能进行任何操作,除了:
    • 作为丢弃值expression式出现( m;是其中之一),或者
    • 检索其值。
  • variables必须是:
    • 一个const ,非易失volatile整数或枚举的初始值是一个常量expression式 ,或
    • 一个constexpr非易失volatilevariables(或这样的子对象)

参考C ++ 14:[expr.const] /2.7,[basic.def.odr] / 3(第一句),[expr.prim.lambda] / 12,[expr.prim.lambda] / 10。

正如其他评论/回答所build议的那样,这些规则的基本原理是编译器需要能够独立于块来“合成”一个没有捕获的lambda作为一个自由函数(因为这样的事情可以被转换为一个指针 – 到函数); 它可以做到这一点,尽pipe如果它知道variables将始终具有相同的值,但是可以重复获取独立于上下文的variables值的过程。 但是,如果variables可能会不时变化,或者variables的地址是必需的,就不能这样做。


在你的代码中, n是由一个非常量expression式初始化的。 因此, n不能在没有被捕获的情况下在lambda中使用。

m由一个常数expression式42初始化,所以它符合“某些标准”。 丢弃值expression式不会使用expression式,所以m; 可以被使用而不被捕获。 gcc是正确的。


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

一个variablesx其名称作为一个可能被评估的expression式exexr使用 ,除非将x的左值转换为右值,产生一个不会调用任何非平凡函数的常量expression式,如果x是一个对象ex是expression式e的潜在结果集合中的一个元素,其中将左值到右值转换应用于e ,或者e是丢弃值expression式。

但在仔细阅读后,它特别提到丢弃值expression式不会使用expression式。

C ++ 11的[basic.def.odr]版本最初没有包含丢弃值expression式,所以在发布的C ++ 11下,clang的行为是正确的。 然而,出现在C ++ 14中的文本被接受为C ++ 11的缺陷( 问题712 ),所以即使在C ++ 11模式下,编译器也应该更新它们的行为。

因为它是一个常量expression式,编译器就好像是[] { 42; }(); [] { 42; }();

[ expr.prim.lambda ]中的规则是:

如果lambdaexpression式或通用lambda odr的函数调用操作符模板的实例化使用(3.2)该variables或具有自动存储持续时间的variables,则该实体应该由lambdaexpression式捕获。

这里有一个来自标准[ basic.def.odr ]的引用:

除非将x的左值转换为右值转换为常数expression式(…)或e是放弃值expression式,否则variablesx的名称显示为潜在评估的expression式ex是odr-使用的。

(删除不是很重要的部分,以保持它的短)

我的简单理解是:编译器知道m在编译时是恒定的,而n在运行时会改变,因此n必须被捕获。 n会被使用,因为在运行时你必须真正看看n里面的内容。 换句话说,“ n只能有一个”定义的事实是相关的。

这是来自MM的评论:

m是一个常量expression式,因为它是一个带有常量expression式初始值设定项的const自动variables,但n不是一个常量expression式,因为它的初始值设定项不是一个常量expression式。 这包含在[expr.const] /2.7中。 根据[basic.def.odr] / 3的第一句,常量expression式不是ODR使用的

看到这里的演示 。

编辑:我的答案以前的版本是错误的。 初学者是对的,这里是相关的标准报价:

[basic.def.odr]

  1. 一个variablesx,其名称作为一个可能被评估的expression式ex,被exr使用 ,除非将x的左值转换为右值 ,产生一个不会调用任何非平凡函数的常量expression式 ,如果x是一个对象ex是expression式e的潜在结果集合中的一个元素,其中将左值到右值转换应用于e,或者e是丢弃值expression式。 …

由于m是一个常数expression式,因此它不会被使用,因此不需要被捕获。

看来,叮当的行为是不符合标准的。