1

This compiles

void f() {
    constexpr bool x{};
    auto g = []{
        if constexpr (x) {
        }
    };
    g();
}

int main () {
    f();
}

and it compiles even if I change [] to [x] or to [&x]. In these two latter cases, though, clangd tells me

Lambda capture 'x' is not required to be captured for this use (fix available) [-Wunused-lambda-capture]

A first minor question would be: is capturing x in this case simply redundant? Or there's more?

But my main question is: from where in the standard should I understand that I can make without any capture in the case above? After all, just writing (void)x; before the if constexpr, for instance, makes the capture ([x] or [&x]) necessary.

Enlico
  • 23,259
  • 6
  • 48
  • 102

2 Answers2

1

A variable is required to be captured if it is a local entity that is odr-used within the lambda expression. See [basic.def.odr]/10. In your example, x is a local entity but it is not odr-used by if constexpr (x) because:

  • x is a variable of non-reference type that is usable in constant expressions and has no mutable subobjects, and
  • the lvalue-to-rvalue conversion is applied immediately.

See [basic.def.odr]/5.2. (In a limited set of situations, the lvalue-to-rvalue conversion is allowed to not be done immediately; see [basic.def.odr]/3.)

In general, [basic.def.odr]/5 is the place to look when you need to determine whether a variable is odr-used.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Would you mind expanding the explanation of the two bullets? Since `x` is `bool` I clearly understand that it _has no mutable subobjects_, as it has no subobjects at all, but what does it mean that it _is a variable of non-reference type that is usable in constant expressions_? what makes it so? – Enlico Sep 01 '22 at 19:13
  • @Enlico it means (to put it short) it’s a compile time constant and its address is not needed, see [here](https://stackoverflow.com/a/45557709/4885321) – alagner Sep 01 '22 at 19:28
  • @Enlico the definition of "usable in constant expressions" is itself too complicated to cover here and needs a separate question, but obviously `x` is usable in constant expressions because the compiler wouldn't accept `if constexpr (x)` otherwise. – Brian Bi Sep 01 '22 at 20:11
  • And what about the _non-reference type_ part? Can I have a _reference type that is usable in constant expression and has no mutable subobjects_? – Enlico Sep 01 '22 at 20:24
  • 1
    @Enlico [basic.def.odr]/5.1 is the rule that applies to references. 5.2 only applies to non-reference variables. – Brian Bi Sep 01 '22 at 20:25
1

The case is ODR-usage (or rather: non-ODR-usage in this particular case) and it's not C++20-specific.

See expr.prim.lambda.capture

If an expression potentially references a local entity within a declarative region in which it is odr-usable, and the expression would be potentially evaluated if the effect of any enclosing typeid expressions (7.6.1.7) were ignored, the entity is said to be implicitly captured by each intervening lambda-expression with an associated capture-default that does not explicitly capture it.

Taking a look into the examples and notes that follow may also be worthwhile:

Note: The set of captured entities is determined syntactically, and entities might be implicitly captured even if the expression denoting a local entity is within a discarded statement (8.5.1). Example:

template<bool B>
void f(int n) {
  [=](auto a) {
    if constexpr (B && sizeof(a) > 4) {
      (void)n; // captures n regardless of the value of B and sizeof(int)
    }
  }(0);
}

In previous standards it was more clearly stated imho, see: https://stackoverflow.com/a/42611583/4885321

alagner
  • 3,448
  • 1
  • 13
  • 25