54 / 163 · C++20 · 9 min
What constexpr, consteval, and constinit each control
constexpr expresses the ability to evaluate as a constant and constraints on variables; consteval requires that an immediate call satisfy constant-expression rules; constinit constrains initialization of variables with static or thread storage duration. The three are not equivalent; in particular constinit does not make a variable read-only.
In this lesson
constexpr: usable in a constant context
A C++11 constexpr variable requires suitable constant initialization and makes the object const. A constexpr function provides the ability to enter constant evaluation, but an ordinary call need not be evaluated during translation. Putting the result into a constexpr variable or a static_assert is what establishes the constant-expression requirement for that call.
From C++14 onward, function-body rules gradually relaxed so loops and local variables could be used in constant computation, but you still must check limits against the target standard. constexpr is not an annotation meaning “always faster”; compilers can already optimize ordinary expressions. It first provides a language-level, checkable contract, not a performance-measurement conclusion.
consteval: the call must satisfy immediate-evaluation rules
C++20 consteval declares an immediate function. An ordinary potentially-evaluated call must satisfy constant-expression requirements; you cannot pass input that is known only at run time. It fits compile-time checks, fixed table generation, and other interfaces that truly must not fall back to run time—not a default decoration for every small function.
In the example, square supports both constant and run-time contexts, while fixed_limit is explicitly an immediate function. Neither automatically gets the right result just because it lexically appears outside main; the deciding factors are the function declaration, the arguments, and the call context. When you expose a tool, first decide whether you truly need to forbid run-time calls.
constinit: controls initialization, not later modification
C++20 constinit applies to variables with static or thread storage duration and requires that there be no dynamic initialization. It can turn an accidentally introduced run-time initialization into a compile error and helps control initialization dependencies across translation units, but it does not make later reads and writes automatically thread-safe, and it does not make the variable const.
The example initializes a global counter as a constant, then increments it in main, proving that constinit and read-only are different things. If you want a constant value that cannot be modified, constexpr is more appropriate. If you need mutable global state and only want to lock down the initialization phase, consider constinit, but first evaluate whether you can reduce shared global state.
Pitfalls
- A
constexprfunction can still run normally with run-time arguments; do not treatconstexpron the function as evidence that every call completed a compile-time computation. constinitis notconst; it does not constrain later writes and does not provide synchronization. Multithreaded modification still needs an appropriate lock or atomic object.
Run an example
Minimum C++20 · complete program · Download .cpp
#include <cassert>
#include <iostream>
constexpr int square(int x) { return x * x; }
consteval int fixed_limit() { return square(4); }
constinit int counter = square(2);
int main() {
constexpr int limit = fixed_limit();
static_assert(limit == 16);
int input = 3;
const int result = square(input);
++counter;
assert(result == 9 && counter == 5);
std::cout << limit << ' ' << result << ' ' << counter << '\n';
}
Compile locally
g++ -std=c++20 -Wall -Wextra -Wpedantic -pthread modern-constexpr.cpp -o example && ./exampleExpected result
16 9 5
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
Why can constinit int local = 4 not be used directly as an ordinary automatic variable in main, while static constinit int local = 4 can?
Show a reference answer
constinit constrains initialization of static or thread storage duration; an ordinary automatic variable inside a block is outside that scope. Adding static gives the variable static storage duration and initializes it with the constant 4, which satisfies the requirement. It can still be assigned later. If you only need an immutable constant inside the block, write constexpr int local = 4.
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.