为什么我可以 "captureless-capture" 一个 int 变量,而不是一个非捕获的 lambda?

Why can I "captureless-capture" an int variable, but not a non-capturing lambda?

以下函数有效(自 C++20 起):

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

即使 l1 没有捕获任何东西,据推测,它仍然可以“无捕获地捕获”b 的值,因为它是 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; };
}

编译失败。为什么?编译器在 lambda 中调用 l1 应该没有问题;那么为什么 b 可以进行无捕获捕获,而 l1 不行?

See this on GodBolt.

这与 odr-use 有关。

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

A local entity is odr-usable in a scope if:

  • either the local entity is not *this, or an enclosing class or non-lambda function parameter scope exists and, if the innermost such scope is a function parameter scope, it corresponds to a non-static member function, and
  • for each intervening scope ([basic.scope.scope]) between the point at which the entity is introduced and the scope (where *this is considered to be introduced within the innermost enclosing class or non-lambda function definition scope), either:
    • the intervening scope is a block scope, or
    • the intervening scope is the function parameter scope of a lambda-expression that has a simple-capture naming the entity or has a capture-default, and the block scope of the lambda-expression is also an intervening scope.

If a local entity is odr-used in a scope in which it is not odr-usable, the program is ill-formed.

所以在这个例子中,a 是 odr-usable 但 b 不是:

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

在这个例子中,类似地,ac 是 odr-usable,但 bl1 都不是。

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

但规则不仅仅是“不是 odr-usable”,它也是“odr-used”。其中哪些是 odr-used?那是 [basic.def.odr]/5:

A variable is named by an expression if the expression is an id-expression that denotes it. A variable x whose name appears as a potentially-evaluated expression E is odr-used by E unless

  • x is a reference that is usable in constant expressions ([expr.const]), or
  • x is a variable of non-reference type that is usable in constant expressions and has no mutable subobjects, and E is an element of the set of potential results of an expression of non-volatile-qualified non-class type to which the lvalue-to-rvalue conversion ([conv.lval]) is applied, or
  • x is a variable of non-reference type, and E is an element of the set of potential results of a discarded-value expression ([expr.context]) to which the lvalue-to-rvalue conversion is not applied.

对于 b * a 的情况,b 是“可用于常量表达式的 non-reference 类型的变量”,我们正在对它做的是应用“lvalue-to-rvalue转换”。这是规则的第二个项目符号例外,所以 b 不是 而不是 odr-used,所以我们没有 odr-used 但没有 odr-usable问题。

对于 l1(c) 的情况,l1 也是“可用于常量表达式的 non-reference 类型的变量”......但我们没有做 lvalue-to-rvalue转换就可以了。我们正在调用呼叫操作员。所以我们没有命中异常,所以我们是 odr-using l1... 但它不是 odr-usable,这使得这个 ill-formed.

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