为什么我可以“无捕获捕获”一个 int 变量,但不能“捕获”非捕获 lambda?

ein*_*ica 5 c++ lambda language-lawyer c++20

以下函数有效(从 C++20 开始):

void foo() {
    constexpr const int b { 123 };
    constexpr const auto l1 = [](int a) { return b * a; };
    (void) l1;
}
Run Code Online (Sandbox Code Playgroud)

即使l1不捕获任何内容,据说它仍然可以“无捕获捕获” 的值b,因为它是 a const(它甚至不必是constexpr;但请参阅@StoryTeller 的评论)。

但如果我尝试在新的 lambda 中捕获更复杂的东西:

void foo() {
    constexpr const int b { 123 };
    constexpr const auto l1 = [](int a) { return b * a; };
    (void) [](int c) { return l1(c) * c; };
}
Run Code Online (Sandbox Code Playgroud)

这无法编译。为什么?l1编译器从 lambda 内部调用应该没有问题;那么为什么b无捕获捕获可以,但l1不行呢?

请在 GodBolt 上查看此内容。

Bar*_*rry 8

这与 ODR 使用有关。

首先,来自[basic.def.odr]/10

如果满足以下条件,则本地实体在某个范围内可使用 odr:

  • 本地实体不是 *this,或者存在封闭类或非 lambda 函数参数范围,并且如果最里面的此类范围是函数参数范围,则它对应于非静态成员函数,并且
  • 对于引入实体的点和范围(其中 *this 被认为是在最内层封闭类或非 lambda 函数定义范围内引入)之间的每个介入范围 ([basic.scope.scope]),可以是:
    • 中间作用域是块作用域,或者
    • 中间作用域是具有命名实体的简单捕获或具有捕获默认值的 lambda 表达式的函数参数作用域,并且 lambda 表达式的块作用域也是中间作用域。

如果本地实体在不可 odr 可用的范围内进行 odr 使用,则该程序格式错误。

所以在这个例子中,aodr 可用,但b不可用:

void foo() {
    constexpr const int b { 123 };
    constexpr const auto l1 = [](int a) { return b * a; };
    (void) l1;
}
Run Code Online (Sandbox Code Playgroud)

类似地,在此示例中, 和acodr 可用的,但 或b或 都不l1是。

void foo() {
    constexpr const int b { 123 };
    constexpr const auto l1 = [](int a) { return b * a; };
    (void) [](int c) { return l1(c) * c; };
}
Run Code Online (Sandbox Code Playgroud)

但该规则不仅是“不可使用 ODR”,而且还“可使用 ODR”。其中哪些是 ODR 使用的?那是[basic.def.odr]/5

如果表达式是表示变量的 id 表达式,则变量由表达式命名。名称显示为潜在计算表达式 E 的变量 x 被 E odr 使用,除非

  • x 是可在常量表达式 ([expr.const]) 中使用的引用,或者
  • x 是非引用类型的变量,可在常量表达式中使用,并且没有可变子对象,E 是非易失性限定的非类类型表达式的潜在结果集合中的一个元素,左值-应用到右值转换 ([conv.lval]),或者
  • x 是非引用类型的变量,E 是未应用左值到右值转换的丢弃值表达式 ([expr.context]) 的潜在结果集的元素。

对于这种b * a情况,b是“可在常量表达式中使用的非引用类型的变量”,我们正在对它执行的操作是应用“左值到右值的转换”。这是该规则的第二个例外,因此b使用odr,因此我们不存在 odr 使用但不可用 odr 的问题。

对于这种l1(c)情况,l1也是“可在常量表达式中使用的非引用类型的变量”...但我们不会对其进行左值到右值的转换。我们正在调用呼叫操作员。所以我们没有遇到异常,所以我们使用了 odr l1...但它不是 odr 可用的,这使得它的格式不正确。

这里的解决方案是捕获l1(使其可使用 odr)或使其static成为全局(使规则变得无关紧要,因为l1不再是本地实体)。

  • @einpoklum 不,“constexpr”变量隐式是“const”,但“constexpr”和“static”是正交的。 (2认同)