Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicitly capture const variable in a template lambda with no capture-default specified

Consider the following code:

auto f() {
  const auto x = 1;
  return [] (auto) { return x; };
}

GCC and MSVC compiles fine but Clang rejected it. Which compiler should I trust? Is that some compiler extension that Clang does not implement yet or Is it just a Clang bug?

like image 429
康桓瑋 Avatar asked Mar 03 '21 12:03

康桓瑋


2 Answers

This is a clang bug.

The rule we have is [basic.def.odr]/9:

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.

In our example:

auto f() {
  const auto x = 1;
  return [] (auto) { return x; };
}

x is not odr-usable in the lambda body because of the intervening scope that does not capture x (neither a simple-capture nor a capture-default).

So, it's not odr-usable. But is it odr-used? No, from [basic.def.odr]/4:

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.prop]) to which the lvalue-to-rvalue conversion is not applied.

The second bullet applies (and conveniently our variable is even named x!) x is usable in constant expressions because it's a constant integral type and E is an lvalue-to-rvalue conversion here.

So x is not odr-usable, but also not odr-used, so there's no problem here.


Indeed, we even have this example in [expr.prim.lambda.capture]/7:

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
  };
}
like image 53
Barry Avatar answered Oct 20 '22 08:10

Barry


Yep, Clang bug.

The applicable rule is from [basic.def.odr]/9:

If a local entity is odr-used in a scope in which it is not odr-usable, the program is ill-formed.

Notably, the rule says "if you odr-use it, it's ill-formed", not "if we can't determine whether it's an odr-use, it's ill-formed".

There is no odr-use of x in any specialization of that function call operator template.

like image 40
T.C. Avatar answered Oct 20 '22 08:10

T.C.