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
.
A const int var can be dynamically set to a value at runtime and once it is set to that value, it can no longer be changed. A constexpr int var cannot be dynamically set at runtime, but rather, at compile time. And once it is set to that value, it can no longer be changed.
const can only be used with non-static member functions whereas constexpr can be used with member and non-member functions, even with constructors but with condition that argument and return type must be of literal types.
A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later. So, what does constexpr mean?
constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time. A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations.
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;
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.
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.
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