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

时间:2017-04-18 08:19:04

标签: c++ lambda const language-lawyer

考虑以下示例:

#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表达式)但我无法找到原因。你能指点我解释一个段落吗?

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

3 个答案:

答案 0 :(得分:50)

对于块范围内的lambda,满足到达范围中某些条件的变量可以在lambda内部以有限的方式使用,即使它们未被捕获。

粗略地说,到达范围包括包含lambda的函数的本地变量,它将在lambda定义的范围内。因此,这包括上述示例中的mn

“特定标准”和“有限方式”具体(截至C ++ 14):

  • 在lambda中,变量不能是 odr-used ,这意味着它不能进行任何操作,除了:
    • 显示为丢弃值表达式(m;是其中之一)或
    • 检索其值。
  • 变量必须是:
    • const,非volatile整数或枚举,其初始化程序为常量表达式,或
    • constexpr,非volatile变量(或其子对象)

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

这些规则的基本原理,正如其他评论/答案所暗示的那样,编译器需要能够将无捕获lambda“合成”为独立于块的自由函数(因为这样的事物可以转换为指向函数的指针);它可以做到这一点,尽管它引用变量,如果它知道变量总是具有相同的值,或者它可以重复过程来获得独立于上下文的变量值。但如果变量可能会不时变化,或者例如需要变量的地址,则无法执行此操作。

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

m由常量表达式42初始化,因此它符合“特定条件”。丢弃值表达式不会使用表达式,因此可以在不捕获m;的情况下使用m。 gcc是对的。

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

  

除非应用左值到右值的转换,否则x的名称显示为可能评估的表达式ex的变量ex odr-used to x产生一个不调用任何非平凡函数的常量表达式,如果x是一个对象,ex是表达式{{e的潜在结果集的一个元素。 1}},其中左值到右值转换应用于e,或e是丢弃值表达式。

但仔细阅读后,它特别提到丢弃值表达式 odr-use 表达式。

C ++ 11的[basic.def.odr]版本最初没有包含丢弃值表达式的情况,因此clang的行为在已发布的C ++ 11下是正确的。但是,C ++ 14中出现的文本被接受为C ++ 11的缺陷(Issue 712),因此即使在C ++ 11模式下,编译器也应该更新它们的行为。

答案 1 :(得分:33)

因为它是一个常量表达式,编译器会将其视为[] { 42; }();

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

  

如果lambda表达式或函数调用的实例化   通用lambda odr的运算符模板 - 使用(3.2)this或a   变量具有自动存储持续时间从其到达范围,   该实体应由lambda表达式捕获。

这是标准[basic.def.odr]的引用:

  

变量x的名称   作为一个可能被评估的表达式出现,除非是使用odr   将lvalue-to-rvalue转换应用于x会产生常量表达式(...)或e是一个废弃值表达式。

(删除不那么重要的部分以保持简短)

我的简单理解是:编译器知道m在编译时是常量,而n将在运行时更改,因此必须捕获nn会被使用,因为您必须在运行时实际查看n内的内容。换句话说,&#34;只有一个&#34; n的定义是相关的。

这是来自M.M的评论:

  

m是一个常量表达式,因为它是一个const自动变量   使用常量表达式初始值设定项,但n不是常量   表达式,因为它的初始化程序不是常量表达式。这个   在[expr.const] /2.7中有介绍。常量表达式不是   根据[basic.def.odr] / 3

的第一句,使用ODR

请在此处查看demo

答案 2 :(得分:2)

编辑:我的答案的先前版本是错误的。初学者是正确的,这是相关的标准引用:

[basic.def.odr]

  
      
  1. 除非将lvalue-to-rvalue转换应用于x,否则名称显示为可能评估的表达式ex的变量x除非是 odr-used ,否则会产生constant expression调用任何非平凡的函数,如果x是一个对象,ex是表达式e的潜在结果集合的元素,其中左值到右值的转换应用于e,或者e是丢弃值表达。 ...
  2.   

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

似乎clangs的行为不符合标准。