Can I use a constexpr value in a lambda without capturing it?
Should this be considered a bug in clang 3.8?
Yep. A capture is only needed if [expr.prim.lambda]/12 mandates so:
Note in particular the highlighted example. f(x)
does not necessitate x
to be captured, because it isn't odr-used (overload resolution selects the overload with the object parameter). The same argumentation applies to your code - [basic.def.odr]/3:
A variable
x
whose name appears as a potentially-evaluated expression
ex
is odr-used byex
unless applying the lvalue-to-rvalue conversion
(4.1) tox
yields a constant expression (5.20) that does not invoke
any non-trivial functions…
This requirement is certainly met.
…and, if
x
is an object,ex
is an element of
the set of potential results of an expressione
, where either the
lvalue-to-rvalue conversion (4.1) is applied toe
, ore
is a
discarded-value expression (Clause 5).
i
is its set of potential results as per [basic.def.odr]/(2.1), and the l-t-r conversion is indeed immediately applied as its passed to a non-type template parameter of object type.
Hence, as we have shown that (12.1) isn't applicable - and (12.2) clearly isn't, either - Clang is wrong in rejecting your snippet.
Access to constexpr variable inside lambda expression without capturing
Are there special rules that apply to
constexpr
for capturing/accessing?
Yes, constexpr
variables could be read without capturing in lambda:
A lambda expression can read the value of a variable without capturing
it if the variable
- has const non-volatile integral or enumeration type and has been initialized with a constant expression, or
- is constexpr and trivially copy constructible.
Constexpr variable captured inside lambda loses its constexpr-ness
Gcc is right. b
(as constexpr
variable) doesn't need to be captured in fact.
A lambda expression can read the value of a variable without capturing
it if the variable
- is constexpr and has no mutable members.
GCC LIVE
It seems if making b
static
then MSVC could access b
without capturing.
template<class T> void f(){
constexpr static bool b=std::is_same_v<T,int>;
auto func_x=[](){
if constexpr(b){
}else{
}
};
func_x();
}
MSVC LIVE
And
How to work around it while keep the compile-time guarantee?
We can't keep the constexpr-ness for the captured variables. They become non-static data members of the lambda closure type and non-static data members can't be constexpr
.
Must constexpr expressions be captured by a lambda in C++?
The code is well-formed. The rule from [expr.prim.lambda] is:
If a lambda-expression or an instantiation of the function call operator template of a generic lambda odr-uses (3.2)
this
or a variable with
automatic storage duration from its reaching scope, that entity shall be captured by the lambda-expression.
Any variable that is odr-used must be captured. Is x
odr-used in the lambda-expression? No, it is not. The rule from [basic.def.odr] is:
A variable
x
whose name appears as a potentially-evaluated expressionex
is odr-used byex
unless applying the lvalue-to-rvalue conversion (4.1) tox
yields a constant expression (5.20) that does not invoke any non-trivial
functions and, ifx
is an object,ex
is an element of the set of potential results of an expressione
, where either the lvalue-to-rvalue conversion (4.1) is applied toe
, ore
is a discarded-value expression (Clause 5).
x
is only used in a context where we apply the lvalue-to-rvalue conversion and end up with a constant expression, so it is not odr-used, so we do not need to capture it. The program is fine. This is the same idea as why this example from the standard is well-formed:
void f(int, const int (&)[2] = {}) { } // #1
void f(const int&, const int (&)[1]) { } // #2
void test() {
const int x = 17;
auto g = [](auto a) {
f(x); // OK: calls #1, does not capture x
};
// ...
}
constexpr variable not captured
When you return x;
in the first example, you have to invoke A
’s copy constructor, which involves binding a reference to x
and thus odr-uses it. The argument can be made that a trivial copy of a value usable in constant expressions shouldn’t constitute an odr-use any more than return x.a;
, but there’s no such exception in that rule, so Clang is correct to reject it.
As a practical matter, you can of course make any constexpr
variable static
to avoid any need to capture it.
Why can I captureless-capture an int variable, but not a non-capturing lambda?
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).
Related Topics
In C++, Is There a Difference Between "Throw" and "Throw Ex"
What Is The Correct Behavior of Pthread_Mutex_Destroy When Destroying a Locked Mutex
What Happens to Interprocess Memory If One of The Processes Dies Unexpectedly
C++ -- Return X,Y; What Is the Point
Gstreamer Recording Video with Audio
Optimal Buffer Size for Write(2)
Compiling Multithread Code with G++ (-Wl,-No-As-Needed Not Working)
Stopping an Infinite Loop in C++ When Key Is Pressed
How to Subtract Two Gettimeofday Instances
Stateful Functors & Stl:Undefined Behaviour
Register an Object Creator in Object Factory
Error: Declaration Does Not Declare Anything
Using Boost Tokenizer Escaped_List_Separator with Different Parameters