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?
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 expressionE
is odr-used byE
unless
x
is a reference that is usable in constant expressions ([expr.const]), orx
is a variable of non-reference type that is usable in constant expressions and has no mutable subobjects, andE
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, orx
is a variable of non-reference type, andE
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
};
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With