Why is `std::is_constant_evaluated()` false for this constant-initialized variable?

Note 2 to [expr.const]/2 implies that if we have a variable o such that:

the full-expression of its initialization is a constant expression when interpreted as a constant-expression, except that if o is an object, that full-expression may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types

then:

Within this evaluation, std​::​is_­constant_­evaluated() […] returns true.

Consider:

#include <type_traits>
int main() {
    int x = std::is_constant_evaluated();
    return x;
}

This program returns 0 when executed.

However, I don’t see how the full-expression of the initialization of x is not a constant expression. I do not see anything in [expr.const] that bans it. Therefore, my understanding of the note (which is probably wrong) implies that the program should return 1.

Now, if we look at the normative definition of std::is_constant_evaluated, it is only true in a context that is “manifestly constant-evaluated”, and the normative definition of the latter, [expr.const]/14, is more clear that the program above should return 0. Specifically, the only item that we really need to look at is the fifth one:

the initializer of a variable that is usable in constant expressions or has constant initialization …

x is not usable in constant expressions, and it doesn’t have constant initialization because no automatic variable does.

So there are two possibilities here. The more likely one is that I haven’t understood the note, and I need someone to explain to me why the note does not imply that the program should return 1. The less likely one is that the note contradicts the normative wording.

Answer

The full quote here is

A variable or temporary object o is constant-initialized if

  • (2.1) either it has an initializer or its default-initialization results in some initialization being performed, and
  • (2.2) the full-expression of its initialization is a constant expression when interpreted as a constant-expression, except that if o is an object, that full-expression may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types. [Note 2: Such a class can have a non-trivial destructor. Within this evaluation, std​::​is_­constant_­evaluated() ([meta.const.eval]) returns true. — end note]

The tricky bit here is that the term “is constant-initialized” (note: not “has constant initialization”) doesn’t mean anything by itself (it probably should renamed to something else). It’s used in exactly three other places, two of which I’ll quote below, and the last one ([dcl.constexpr]/6) isn’t really relevant.

[expr.const]/4:

A constant-initialized potentially-constant variable V is usable in constant expressions at a point P if V‘s initializing declaration D is reachable from P and […].

[basic.start.static]/2:

Constant initialization is performed if a variable or temporary object with static or thread storage duration is constant-initialized ([expr.const]).

Let’s replace “constant-initialized” with something less confusing, like “green”.

So

  • A green potentially-constant variable is usable in constant expressions if [some conditions are met]
  • Constant initialization is performed if a variable or temporary object with static or thread storage duration is green.

Outside of these two cases, the greenness of a variable doesn’t matter. You can still compute whether it is green, but that property has no effect. It’s an academic exercise.

Now go back to the definition of greenness, which says that a variable or temporary object is green if (among other things) “the full-expression of its initialization is a constant expression when interpreted as a constant-expression” with some exceptions. And the note says that during this hypothetical evaluation to determine the green-ness of the variable, is_constant_evaluated() returns true – which is entirely correct.

So going back to your example:

int main() {
    int x = std::is_constant_evaluated();
    return x;
}

Is x green? Sure, it is. But it doesn’t matter. Nothing cares about its greenness, since x is neither static nor thread local nor potentially-constant. And the hypothetical computation done to determine whether x is green has nothing to do with how it is actually initialized, which is governed by other things in the standard.