The following function is valid (as of C 20):
void foo() {
constexpr const int b { 123 };
constexpr const auto l1 = [](int a) { return b * a; };
(void) l1;
}
even though l1
does not capture anything, supposedly, it is still allowed to "captureless-capture" the value of b
, as it is a const
(it doesn't even have to be constexpr
; but see @StoryTeller's comment).
But if I try to capture something more complex in a new lambda:
void foo() {
constexpr const int b { 123 };
constexpr const auto l1 = [](int a) { return b * a; };
(void) [](int c) { return l1(c) * c; };
}
This fails to compile. Why? The compiler should have no trouble calling l1
from within the lambda; so why is b
ok for captureless-capture, and l1
isn't?
CodePudding user response:
This has to do with odr-use.
First, from [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.
So in this example, a
is odr-usable but b
is not:
void foo() {
constexpr const int b { 123 };
constexpr const auto l1 = [](int a) { return b * a; };
(void) l1;
}
And in this example, similarly, the a
and c
are odr-usable, but neither b
or nor l1
are.
void foo() {
constexpr const int b { 123 };
constexpr const auto l1 = [](int a) { return b * a; };
(void) [](int c) { return l1(c) * c; };
}
But the rule isn't just "not odr-usable", it's also "odr-used". Which one(s) of these are odr-used? That's [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.
For the b * a
case, b
is "a variable of non-reference type that is usable in constant expressions" and what we're doing with it is applying "the lvalue-to-rvalue conversion". That's the second bullet exception to the rule, so b
is not odr-used, so we don't have the odr-used but not odr-usable problem.
For the l1(c)
case, l1
is also "a variable of non-reference type that is usable in constant expressions"... but we're not doing an lvalue-to-rvalue conversion on it. We're invoking the call operator. So we don't hit the exception, so we are odr-using l1
... but it's not odr-usable, which makes this ill-formed.
The solution here is to either capture l1
(making it odr-usable) or make it static
or global (making the rule irrelevant since l1
wouldn't be a local entity anymore).