constinit specifier (since C++20)

< cpp‎ | language


[edit] Explanation

The constinit specifier declares a variable with static or thread storage duration. If a variable is declared with constinit, its initializing declaration must be applied with constinit. If a variable declared with constinit has dynamic initialization, the program is ill-formed. If no constinit declaration is reachable at the point of the initializing declaration, the program is ill-formed, no diagnostic required.

constinit cannot be used together with constexpr or consteval. When the declared variable is a reference, constinit is equivalent to constexpr. When the declared variable is an object, constexpr mandates that the object must have static initialization and constant destruction and makes the object const-qualified, however, constinit does not mandate constant destruction and const-qualification. As a result, an object of a type which has constexpr constructors and no constexpr destructor (e.g. std::shared_ptr<T>) might be declared with constinit but not constexpr.

const char *g() { return "dynamic initialization"; }
constexpr const char *f(bool p) { return p ? "constant initializer" : g(); }
constinit const char *c = f(true); // OK
// constinit const char *d = f(false); // error

constinit can also be used in a non-initializing declaration to tell the compiler that a thread_local variable is already initialized.

extern thread_local constinit int x;
int f() { return x; } // no check of a guard variable needed

[edit] Keywords


[edit] Example

// static_init_counter.cpp
#include <mutex>
#include <cstdint>
std::mutex g_unsafe_mutex;
std::uint32_t g_unsafe_data{ 0 };
constinit std::mutex g_safe_mutex;
constinit std::uint32_t g_safe_data{ 0 };
std::uint32_t unsafe_increment() {
    std::lock_guard guard(g_unsafe_mutex); // Note usage of C++17 CTAD
    return g_unsafe_data;
std::uint32_t safe_increment() {
    std::lock_guard guard(g_safe_mutex); // Note usage of C++17 CTAD
    return g_safe_data;
// static_init_counter.h
std::uint32_t unsafe_increment();
std::uint32_t safe_increment();
// static_init_data.cpp
#include "static_init_counter.h"
struct unsafe {
    std::uint32_t index = unsafe_increment();
struct safe {
    std::uint32_t index = safe_increment();
// May be initialized before g_unsafe_mutex/g_unsafe_data is initialized, thus invoking UB
// This is the static initialization order fiasco
unsafe unsafe_object;
// Is dynamically initialized, i.e. at runtime
// Since g_safe_mutex/g_safe_data is statically initialized, i.e. at compile-time, no UB is invoked
safe safe_object;

[edit] See also