为什么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定义的范围内。 所以这在上面的例子中包括了m
和n
。
“特定的标准”和“有限的方式”是特别的(从C ++ 14开始):
- 在lambda里面,variables不能被使用 ,这意味着它不能进行任何操作,除了:
- 作为丢弃值expression式出现(
m;
是其中之一),或者 - 检索其值。
- 作为丢弃值expression式出现(
- variables必须是:
- 一个
const
,非易失volatile
整数或枚举的初始值是一个常量expression式 ,或 - 一个
constexpr
非易失volatile
variables(或这样的子对象)
- 一个
参考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的第一句话相当复杂:
一个variables
x
其名称作为一个可能被评估的expression式ex
被exr使用 ,除非将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]
- 一个variablesx,其名称作为一个可能被评估的expression式ex,被exr使用 ,除非将x的左值转换为右值 ,产生一个不会调用任何非平凡函数的常量expression式 ,如果x是一个对象ex是expression式e的潜在结果集合中的一个元素,其中将左值到右值转换应用于e,或者e是丢弃值expression式。 …
由于m
是一个常数expression式,因此它不会被使用,因此不需要被捕获。
看来,叮当的行为是不符合标准的。