Constinit in C++ (ENG)

Make Compile-Time Great Again (C++20, Part II)

We have discussed constexpr and consteval, so in this note, let's delve into the details of constinit. The constinit specifier was introduced in C++20 along with consteval. While consteval specifies that a function must produce a constant expression at compile-time, it is easy to guess that constinit is used to ensure a variable must be initialized at compile-time, quite similar to how consteval applies to functions.

To use constinit, the approach differs from what we do with consteval. We mentioned that constexpr can be used anywhere, but it does not guarantee compile-time function execution. This is where consteval comes in—it is more strict and provides guaranteed compile-time function evaluation (CTFE) semantics. So, does the constinit specifier simply provide compile-time initialization to variables compared to constexpr? Nuh-uh!

The constinit specifier is only allowed to declare variables with static storage duration(global variable has this static semantics by default) and thread-local variables. This means that regular stack variables(automatic storage duration) cannot be declared as constinit. Here's some examples:

constinit int globalVar = 100; // Allowed because globalVar has static storage duration
int main() {
    constexpr int localVar = 100; // okay 
    constinit int localVar2 = 100; // Error: localVar has automatic storage 
    constinit static int staticVar = 100; // okay 
    return 0;
}

In contrast to static storage duration, we have dynamic storage duration too. Objects with dynamic storage duration are often created and destroyed using the new and delete keywords. Since these objects are allocated and deallocated at run-time, you cannot force the compiler to allocate any memory for them at compile-time. Therefore, if you try to declare a variable with dynamic storage duration as constinit, it will cause you an error.