Yes, the diagnostic is correct. constexpr variables must be initialized with a constant expression, and a is not a constant expression (it's a mutable variable).
The purpose of constinit (P1143) is to force a variable declaration to be ill-formed if it's initialization is not constant. It doesn't change anything about the variable itself, like it's type or anything (in the way that constexpr is implicitly const). Silly example:
struct T {
int i;
constexpr T(int i) : i(i) { }
T(char c) : i(c) { }
};
constinit T c(42); // ok
constinit T d('X'); // ill-formed
That is all constinit is for, and the only real rule is [dcl.constinit]/2:
If a variable declared with the constinit specifier has dynamic initialization ([basic.start.dynamic]), the program is ill-formed.
[ Note: The constinit specifier ensures that the variable is initialized during static initialization ([basic.start.static]).
— end note
]
The const in constinit refers only to the initialization, not the variable, not any types. Note that it also doesn't change the kind of initialization performed, it merely diagnoses if the wrong kind is performed.
In:
constinit int a = 0;
constexpr int b = a;
0 is a constant expression, so the initialization of a is well-formed. Once we get past that, the specifier doesn't do anything. It's equivalent to:
int a = 0; // same behavior, a undergoes constant initialization
constexpr int b = a;
Which is straightforwardly ill-formed.
but at constant-initialization, its value is known, so it could be used to initialize b.
Sure, at this moment. What about:
constinit int a = 0;
cin >> a;
constexpr int b = a;
That's obviously not going to fly. Allowing this would require extending what a constant expression is (already the most complex rule in the standard, in my opinion) to allow for non-constant variables but only immediately after initialization? The complexity doesn't seem worth it, since you can always write:
constexpr int initializer = 0;
constinit int a = initializer;
constexpr int b = initializer;
bis global. We already have differing rules for globals, so maybe the standard could allow this, ifbis global.void foo(){ int a = 0; constexpr int b = a; },ais not a constant expression.ato be treated as a constant expression depending on how it is initialized and on the context it is being used in? C++ already suffers a lot from context-sensitivity.