constinit vs constexpr in C++: Timing and Mutability
In modern C++, both constexpr and constinit
play critical roles in ensuring variables are initialized before runtime
execution begins, but they serve distinct purposes regarding how data
can be modified. While constexpr (introduced in C++11)
forces both compile-time evaluation and absolute immutability,
constinit (introduced in C++20) specifically guarantees
compile-time static initialization without enforcing const
semantics. This article breaks down the exact differences between
constinit and constexpr, focusing directly on
initialization timing, object mutability, and their practical
applications.
Initialization Timing
The primary goal of initialization keywords in C++ is to control when an expression or variable is evaluated.
constexpr: Aconstexprvariable must be evaluated at compile time. The compiler computes the value during translation, substituting the result directly where applicable. It can be used in contexts requiring compile-time constants, such as template arguments, array bounds, orstatic_assertexpressions.constinit: Aconstinitspecifier enforces that a variable with static or thread-local storage duration is initialized at compile time (static initialization) rather than during runtime (dynamic initialization). Its primary purpose is to eliminate the static initialization order fiasco, where the initialization order of global variables across different translation units is undefined. Unlikeconstexpr,constinitcannot be applied to local automatic variables.
| Specifier | Permitted Storage Duration | Evaluation Phase |
|---|---|---|
constexpr |
Automatic (local), static, or thread-local | Compile time |
constinit |
Static or thread-local only | Compile time (guarantees static initialization) |
Mutability
The most significant functional difference between the two specifiers is whether the resulting variable can be modified at runtime.
constexprimpliesconst: Declaring a variableconstexprimplicitly marks it asconst. Once initialized, its value is fixed in read-only memory or inlined, meaning it cannot be modified at runtime.constinitdoes not implyconst: Aconstinitvariable is mutable by default unless explicitly combined with theconstkeyword (constinit const). This allows a program to initialize a global or thread-local variable safely before runtime starts, while still allowing the variable's state to be modified during program execution.
// Evaluated at compile time; immutable at runtime
constexpr int max_users = 100;
// max_users = 200; // Error: assignment of read-only variable
// Initialized at compile time; mutable at runtime
constinit int active_connections = 0;
void on_connect() {
// Valid: constinit does not imply const
++active_connections;
}
// Initialized at compile time; immutable at runtime
constinit const int buffer_size = 4096;Usability in Constant Expressions
Because constinit allows mutability, it does not
guarantee that the variable itself remains a constant expression
throughout execution.
- A
constexprvariable can always be used in subsequent constant expressions, such as defining the size of astd::arrayor evaluating astatic_assert. - A
constinitvariable (without an explicitconst) cannot be used in a constant expression, because its value might change at runtime.
constexpr int a = 10;
constinit int b = 20;
// Allowed: 'a' is a compile-time constant expression
int arr1[a];
// Error: 'b' is not usable in a constant expression
// int arr2[b]; Summary of Differences
- Choose
constexprwhen an object's value is known at compile time and should never change. - Choose
constinitfor global or thread-local variables to ensure they are initialized statically before runtime, avoiding initialization order issues while preserving the ability to update their values during execution.