为什么我可以 "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
不行?
这与 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;
}
在这个例子中,类似地,a
和 c
是 odr-usable,但 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; };
}
但规则不仅仅是“不是 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
不再是本地实体)。
以下函数有效(自 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
不行?
这与 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;
}
在这个例子中,类似地,a
和 c
是 odr-usable,但 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; };
}
但规则不仅仅是“不是 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
不再是本地实体)。