constexpr, consteval, constinit
Three keywords, three contracts: can-run-at-compile-time, must-run-at-compile-time, and must-be-initialized-statically.
constexpr โ "can be constant"
On variables: the initializer must be a constant expression. On functions: the function may run at compile time when given constant arguments โ and still runs at runtime otherwise. One implementation, both worlds:
constexpr int fib(int n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); }
constexpr int F = fib(10); // compile time
int x = fib(runtimeInput); // runtime, same body
consteval โ "must be immediate"
An immediate function: every call produces a compile-time constant; a runtime call is a hard error. Use for constructors of "compile-time-only" types and for functions whose result must be baked in:
consteval int square(int n) { return n * n; }
constexpr int S = square(9); // OK
// int y = square(runtimeVal); // error: not a constant expression
constinit โ "initialized statically, checked at compile time"
constinit guarantees static initialization (no runtime constructor, no SIOF โ static initialization order fiasco) while keeping the variable mutable:
constinit std::atomic<unsigned> requests{0}; // zero-init at load, mutable later
The rule of thumb: constexpr = value is fixed; constinit = initialization is fixed, value is not.