113

constinit is a new keyword and specifier in C++20 which was proposed in P1143.

The following example is provided in the standard:

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);    // ill-formed

A few questions come to mind:

  • What does constinit mean? Why was it introduced? In which cases should we use it?

  • Does it make a variable immutable? Does it imply const or constexpr?

  • Can a variable be both const and constinit? What about constexpr and constinit?

  • To which variables can the specifier be applied? Why cannot we apply it to non-static, non-thread_local variables?

  • Does it have any performance advantages?

This question is intended to be used as a reference for upcoming questions about constinit in general.

2 Answers 2

119
  • What does constinit mean? Why was it introduced? In which cases should we use it?

Initializing a variable with static storage duration might result in two outcomes¹:

  1. The variable is initialized at compile-time (constant-initialization);

  2. The variable is initialized the first time control passes through its declaration.

Case (2) is problematic because it can lead to the static initialization order fiasco, which is a source of dangerous bugs related to global objects.

The constinit keyword can only be applied on variables with static storage duration. If the decorated variable is not initialized at compile-time, the program is ill-formed (i.e. does not compile).

Using constinit ensures that the variable is initialized at compile-time, and that the static initialization order fiasco cannot take place.


  • Does it make a variable immutable? Does it imply const or constexpr?

No and no.

However, constexpr does imply constinit.


  • Can a variable be both const and constinit? What about constexpr and constinit?

It can be both const and constinit. It cannot be both constexpr and constinit. From the wording:

At most one of the constexpr, consteval, and constinit keywords shall appear in a decl-specifier-seq.

constexpr is not equivalent to const constinit, as the former mandates constant destruction, while the latter doesn't.


  • To which variables can the specifier be applied? Why cannot we apply it to non-static, non-thread_local variables?

It can only be applied to variables with static or thread storage duration. It does not make sense to apply it to other variables, as constinit is all about static initialization.


  • Does it have any performance advantages?

No. However, a collateral benefit of initializing a variable at compile-time is that it doesn't take instructions to initialize during program execution. constinit helps developers ensure that is the case without having to guess or check the generated assembly.


¹: See https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables

Sign up to request clarification or add additional context in comments.

9 Comments

"cannot be both constexpr and constinit" Is that because constexpr effectively implies constinit?
"constexpr does imply constinit" - I'd not say so, since constexpr might be applied to local variables, while constinit cannot.
"It can be both const and constinit". Is that effectively the same as constexpr? Also can you clarify why it does not make sense to apply constinit to local variables?
"constexpr mandates constant destruction". Can you just hint what does this means?
@meda, probably that the type needs a constexpr destructor. (Before C++20, the destructor had to be trivial, i.e. = default.)
|
4

MAIN PROBLEM:

An object is considered to be initialized only when the control passes through its declaration or its definition; otherwise (i.e. if the control first jumps into another location within the the source file in which this object is declared or defined) any access to this uninitialized object is undefined behavior.

Moreover, the order of initializing static-duration objects defined into different translation units is also undefined. You don't have a way in code to request the compiler to initialize a static object before or after another one in a different translation unit (even if you'd like to because one object is depending on the other). Actually you cannot do this. It's up to the compiler to decide which object should be initialized first; in particular this actually depends on the order in which each source file's object file is linked.

Example - Segmentation fault

// header.h
struct A {
    int m_x;
    A(int);
};

struct B {
    int m_x;
    B(int);
    int f(); 
};

extern B b;

// main.cpp
#include "header.h"
B b{ 20 };      // declaring an object of class B with static duration.
int main() {}

// src1.cpp
#include "header.h"
A a{ 10 };      // declaring an object of class A with static duration.
A::A(int x): m_x(x) { b.f(); }

//src2.cpp
#include "header.h"
int B::f() { return m_x; }
B::B(int x):  m_x(x) { }

g++ main.cpp src1.cpp src2.cpp // OK: main.cpp should be compiled first
g++ main.cpp src2.cpp src1.cpp // OK: main.cpp should be compiled first
g++ any_other_order // Segfault, maybe, or just access to uninitialized 'b' object's data.

WORKAROUND:

constinit gets introduced in C++20

4 Comments

It actually depends on the order the object files are linked. See Static variables initialisation order.
Could you explain why main.cpp must be compiled first? It makes no sense to me.
@Bolpat: The g++ executable compiles files first, in the order listed, and then links the resulting .o files, in the order listed, to produce a final executable. It's actually the order of linking that matters. When the .o files are combined by the linker, the global initializers for whichever file is linked first will typically (but this is not required!) be included in the linked "global initializers" segment within the executable before the global initializers for whichever file is linked second, and will therefore be executed first at runtime.
On closer look, I noticed a bug in the example code which was probably what was confusing people: Object 'b' needed to be constructed before 'a', or else 'a's constructor could access an uninitialized object. I edited the code to fix this. Note that just calling a function on an unconstructed object probably won't actually trigger a segfault unless the function is virtual (in which case the uninitialized vtable will likely cause a crash). Still, any data accessed by the function wouldn't have been initialized.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.