Reputation: 29942
Look at this little example:
constinit int a = 0;
constexpr int b = a;
clang doesn't compile it (godbolt):
2:15: error: constexpr variable 'b' must be initialized by a constant expression
Is this correct diagnostics?
If yes, why doesn't the standard allow this? I understand, that a
's value may change during running (or even during dynamic-initialization), but at constant-initialization, its value is known, so it could be used to initialize b
.
Upvotes: 5
Views: 1428
Reputation: 10962
is this correct diagnostics?
I would say yes. According to cppreference:
constinit - specifies that a variable must have static initialization, i.e. zero initialization and constant initialization, otherwise the program is ill-formed.
Static (constant) initialization and constant expression are different concepts in that a constant expression may be used in a constant initialization but not the other way around. constinit
shouldn't be confused with const. It means the initialization (only) is constant.
However, constinit const
can be used in a constexpr
and they are supposed to be the same.
Counter-example:
constinit int a = 0;
struct X{
X() {
a = 4;
}
};
X x{};
constexpr int b = a;
What is b
supposed to be ?
The point is that a
can be changed in non-const ways before b
is seen.
Upvotes: 1
Reputation: 302757
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: Theconstinit
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;
Upvotes: 4
Reputation: 45654
constexpr
combines constinit
and const
without exception.
constinit
forces initialization with a compiletime constant expression, and during static initialization, disallowing dynamic initialization. It does not change the variable itself in any way though.
const
forbids changing the variable, though can be weakened by mutable
members.
Both together make it a compiletime constant expression.
In summary, yes, the diagnostic is right.
Upvotes: 2