avatarGajendra Gulgulia

Summarize

C++20 constinit specifier

In this three part tutorial series, we explore the new specifier, de>constinit , and de>consteval added in C++20 as a core language feature and compare its subtle differences with constexpr specifier that is available since C++11.

In the first part I explain the constinit specifier. The most simple explaination of constinit is that it guarantees that the variable is initialized at compile time and when the initialization is not possible we get a compilation error.

1. What does constinit mean?

constinit specifier can be used only with static storage duration variables to and it guarantees compile time initialization of variables. Lets see this in action. To demonstrate the examples, it would be good to know how to identify when a variable has a static storage duration.

const int gConstInt{9}; //const global variable has static storage
static int gStaticInt{9}; //global variables with static specifier
                          //are same as gConstInt
static const int gStaticConstInt{9}; //static and const together on 
                                    // a global variable is trivial
int gInt{9};            //otherwise global variables have automatic 
                        //storage duration
const int gConstIntDefault; //compilation error since const     
                            //variables cannot be uninitialzed
void localMethod(){
    int localVar1{9};     //local variables have automatic storage 
    int localVar2;        //duration too
}

In the above cases, apart from the cases where const qualifier appears before a variable, the variables can be modified later during compile or runtime context.

int main(){
    gConstInt       = 8; // compilation error
    gStaticInt      = 8; //Ok
    gStaticConstInt = 8; //compilation error
    gInt            = 8; //Ok
    constinit int lConstinitInt{9}; //compilation 
}

However the static storage duration variables declared above doesn’t guarantee that the variables are initialized (except for the case where const— qualifier appears before the variable). To have such a guarantee, constinit specifier can be used. constinit , however doesn’t imply that the variables are constant. Variables with constinit specifier can be changed at run- or compile-time contexts

int foo(){return 9;}
constexpr int bar(){return 9;}
constinit int gConstinitInt1{9};
constinit const int gConstinitConstInt{9};
constinit int gConstinitInt2 = foo(); //compilation error since foo 
                                      //is not constexpr
constinit int gConstinitInt3 = bar(); //Ok since bar is constexpr
int localMethod2(){
    constinit localVar3{9};  //compilation error, local variables  
                             //are not static storage and hence 
                             //cannot be constinit
    static constinit int localVar4{9}; //okay since static keyword 
                                       //used to specify static 
                                       //storage for local var
}
int main(){
    gConstinitInt = 8;       //Ok 
    gConstinitConstInt = 8;  //compilation error
}

2. What is the difference constinit and constexpr?

constexpr implies constinit but vice-versa is not true. constexpr variables are required to be initialized at compile time just like constinit variables but behave like compile-time constants and cannot be changed later, unlike constinit variables

constexpr int gConstexprInitialized{9};  //okay
constexpr int gConstexprUninitialized;   //compilation error
constinit int gConstinitInt{9};
int main(){
    gConstexprInitialized = 8; //compilation error: constexpr 
                               //variables cannot be modified
    gConstInitInt = 8;        //Ok 
}

Moreover the compile time evaluated context where constexpr variables can be used doesn’t apply to constinit variables. For e.g.:

#include <array>
constexpr std::size_t getArraySizeBasedOnArchitecture(){
    return sizeof(std::size_t*100);
}
constinit auto arrSize2 = getArraySizeBasedOnArchitecture();
constexpr auto arrSize1 = getArraySizeBasedOnArchitecture();
int main(){
     std::array<int, arrSize1> = intArray1;  //Ok
     std::array<int, arrSize2> intArray2;   //compilation error
}

Also, if one takes a closer look on the assembly code for constexpr and constinit variables, one can easily see that variables with former have no machine code while the latter has. This can be seen in the image below taken from compiler explorer where the variable var1 having constinit specifier has a corresponding assembly code whereas the var2 doesn’t have any corresponding assembly code.

Image source: compiler explorer (www.godbolt.org)

The illustration above doesn’t imply that constinit is inferior to constexpr but it shows the subtle differences that might not be obvious at first glance. Both have their use (as already stated constinit variables can be modified at runtime whereas constexpr variables cannot) depending on the context (compile time vs runtime) we want to program.

Lastly, it is useful to remember that both constexpr and constinit cannot appear together on a variable. It results in compilation error. However, as shown in code snippets, constinit can appear with static (makes sense only in local scope variables) and const qualifier.

3. Where can it be useful ?

Apart from the obvious observations to guarantee that variables are initialized at compile time and that compiler generates error when the variables cannot be initialized at compile time, constinit is very useful to overcome the challenges related to static initialization order fiasco. For more details look at this blog.

4. Summary

A short summary of main points form this article are below:

  1. constinit specifier applies only to variables
  2. constinit guarantees that variables are initialized during compilation, else we get a compilation error.
  3. constinit specifier implies static storage duration however the reverse is not true
  4. constexpr variables imply constinit , however the reverse is not true.
  5. Only one of constexpr and constinit specifier can appear on a variable.
  6. constinit can be applied to const — qualified variables.

5. Conclusion

Prefer constinit via constinit for both constant and non-constant variables whenever the variables are expected or required to be initialized during compilation. Whenever the variables are not initialized at compile time, the compiler throws an error.

In the next article in the series, I’ll explain the usage of consteval specifier. and its difference and similarities to constexpr specifier. Stay tuned!

Modern Cpp
Cplusplus
C Plus Plus Language
Cpp
Recommended from ReadMedium