What is `constinit` in C++20?
- What does
constinit
mean? Why was it introduced? In which cases should we use it?
Initializing a variable with static storage duration might result in two outcomes¹:
The variable is initialized at compile-time (constant-initialization);
The variable is initialized the first time control passes through its declaration.
Case (2) is problematic because it can lead to the static initialization order fiasco, which is a source of dangerous bugs related to global objects.
The constinit
keyword can only be applied on variables with static storage duration. If the decorated variable is not initialized at compile-time, the program is ill-formed (i.e. does not compile).
Using constinit
ensures that the variable is initialized at compile-time, and that the static initialization order fiasco cannot take place.
- Does it make a variable immutable? Does it imply
const
orconstexpr
?
No and no.
However, constexpr
does imply constinit
.
- Can a variable be both
const
andconstinit
? What aboutconstexpr
andconstinit
?
It can be both const
and constinit
. It cannot be both constexpr
and constinit
. From the wording:
At most one of the
constexpr
,consteval
, andconstinit
keywords shall appear in a decl-specifier-seq.
constexpr
is not equivalent to const constinit
, as the former mandates constant destruction, while the latter doesn't.
- To which variables can the specifier be applied? Why cannot we apply it to non-
static
, non-thread_local
variables?
It can only be applied to variables with static or thread storage duration. It does not make sense to apply it to other variables, as constinit
is all about static initialization.
- Does it have any performance advantages?
No. However, a collateral benefit of initializing a variable at compile-time is that it doesn't take instructions to initialize during program execution. constinit
helps developers ensure that is the case without having to guess or check the generated assembly.
¹: See https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables
What's the real difference between constinit and constexpr ?
A constinit
variable is constant initialized, but it is not usable in a constant expression, nor even automatically constant. Your main
can legally contain this line
c2 = 2;
Yup, modification is possible after initialization.
Can C++20 `constinit` waive the need for nifty counter idiom?
Can C++20
constinit
waive the need for nifty counter idiom?
No.
Static initialisation order fiasco is only a problem with dynamic initialisation phase of static objects. Sure, if you don't do dynamic initialisation, then there is no problem, and constinit
enforces that. But that doesn't solve anything when you need dynamic initialisation.
C++20 consteval functions and constexpr variables - are they guaranteed to be evaluated at compilation time?
The standard does not have the concept of "compile time". There is only "constant evaluation" and a "constant expression". Implementations may implement "constant expressions" such that they are evaluated at compile time.
But there is no C++ standard-defined behavior you can point to in the actual C++ program that makes a "constant expression" equivalent to compile-time compilation. It is 100% legal for a compiler to emit code for constant expressions, and it always has been.
From [expr.const]/13:
"An immediate invocation [ie: calling a
consteval
function] shall be a constant expression
So calling a consteval
function is a constant expression. Does this mean that the compiler certainly will not emit actual assembly for this function that gets called at runtime? No; that's a matter of the quality of the implementation.
However, certain uses of a constant expression do inform various compiler decisions. This means that, while the compiler does not in general have to evaluate them at compile time, if the value is used in a place that affects the basic act of compilation, the compiler must in that instance evaluate the expression right then.
For example:
std::is_same_v<array<int, some_constexpr_func(3, 4)>, array<int, 7>>
Whether this (constant) expression evaluates to true
or false
depends on what some_constexpr_func(3, 4)
returns. If it returns the sum of its parameters, it is true
. And the compiler needs to know that, because it needs to emit the code for the std::array<int, S>
type, which as part of its type includes its template parameters. So it needs to know what they are.
Outside of cases where constant evaluation affects compiler decisions, whether any constant evaluation happens at compile time is up to the quality of your compiler.
Are static constinit member variables identical to non-type template parameters?
constinit
has exactly and only one semantic: the expression used to initialize the variable must be a constant expression.
That's it. That's all it does: it causes a compile error if the initializing expression isn't a valid constant expression. In every other way, such a variable is identical to not having constinit
there. Indeed, the early versions of the proposal had constinit as an attribute rather than a keyword, since it didn't really do anything. It only gave a compile error for invalid initialization of the variable.
For this case, there is no reason not to make the variable constexpr
. You clearly don't intend to change the variable, so there's no point in making the variable modifiable.
Is east constexpr / constinit / consteval allowed in C++20?
Sort of.
It's part of the decl-specifier-seq, and the specifiers in that can be in any order. It's the same rule that allows you to write volatile int static long unsigned inline long const x = 1;
But it isn't part of the declarator (in particular, the ptr-operator), so you can't do int* constexpr x = nullptr;
.
Using constinit variable to initialize a constexpr variable
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;
Related Topics
Why Doesn't Left Bit Shift << Shift Beyond 31 for Long Int Datatype
2D-Array as Argument to Function
How Does Virtual Method Invocation Work in C++
Hash an Arbitrary Precision Value (Boost::Multiprecision::Cpp_Int)
C++/C Function Pointers That Return Void*
Do You (Really) Write Exception Safe Code
What Happens When a Computer Program Runs
How to Create a Shared Library with Cmake
Insert VS Emplace VS Operator[] in C++ Map
Is There a Working C++ Refactoring Tool
Why Are By-Value Parameters Excluded from Nrvo
Find All Substring's Occurrences and Locations
Passing Rvalues Through Std::Bind
Cmake Unable to Determine Linker Language with C++
Getting "Source Type Is Not Polymorphic" When Trying to Use Dynamic_Cast