Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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?

See this on GodBolt.

>Solution :

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 converion". 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 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).

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading