How to Use a Constexpr Value in a Lambda Without Capturing It

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:

Sample Image

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 by ex unless applying the lvalue-to-rvalue conversion
(4.1) to x 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 expression e, where either the
lvalue-to-rvalue conversion (4.1) is applied to e
, or e 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 expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.20) that does not invoke any non-trivial
functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e 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



Leave a reply



Submit