constinit 지정자

constinit 지정자 (constinit specifier)

constexpr 변수는 값이 컴파일 타임에 계산되지만 동시에 const가 돼서 나중에 바꿀 수 없게 돼요. 그런데 정적·스레드 저장 기간 변수에 "초기화만 컴파일 타임에 하되, 그 값은 나중에 바꿀 수 있게" 하고 싶을 때가 있어요. constinit 지정자가 바로 그런 용도예요. 이 페이지에서 constinit의 규칙을 살펴볼게요.

출처: cppreference

본문

constinit 지정자는 정적(static) 또는 스레드(thread) 저장 기간을 가진 변수를 선언해요.

constinit 지정자는 구조화 바인딩(structured binding) 선언에도 적용할 수 있어요. 이 경우 constinit은 그 선언이 도입하는 고유 이름 변수에도 적용돼요. (since C++26)

변수가 constinit으로 선언되면 그 초기화 선언에도 constinit이 적용되어야 해요. constinit으로 선언된 변수가 동적 초기화(정적 초기화로 수행되더라도)를 가진다면 프로그램은 ill-formed예요.

초기화 선언 지점에서 어떤 constinit 선언에도 도달할 수 없다면, 프로그램은 진단 없이 ill-formed예요(no diagnostic required).

constinitconstexpr과 함께 쓸 수 없어요. 선언된 변수가 참조일 때 constinitconstexpr과 동등해요. 선언된 변수가 객체일 때 constexpr은 그 객체가 정적 초기화와 상수 파괴를 가져야 하고 객체를 const로 한정하라고 강제하지만, constinit은 상수 파괴나 const 한정을 강제하지 않아요. 그래서 constexpr 생성자를 가지면서 constexpr 소멸자는 없는 타입(예: std::shared_ptr<T>)의 객체는 constexpr로는 선언할 수 없지만 constinit으로는 선언할 수 있어요.

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은 초기화하지 않는 선언에서도 쓸 수 있어요. 그렇게 하면 thread_local 변수가 이미 초기화됐다는 걸 컴파일러에게 알려서, 그렇지 않았을 때 숨은 가드 변수(hidden guard variable)가 부과하던 오버헤드를 줄여줘요.

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

참고 (Notes)

피처 테스트 매크로 (Feature-test macro)

매크로 표준 피처
__cpp_constinit 201907L (C++20) constinit

키워드 (Keywords)

constinit

예제 (Example)

#include <cassert>

constexpr int square(int i)
{
    return i * i;
}

int twice(int i)
{
    return i + i;
}

constinit int sq = square(2);    // OK: initialization is done at compile time
// constinit int x_x = twice(2); // Error: compile time initializer required

int square_4_gen()
{
    static constinit int pow = square(4);
    
    // constinit int prev = pow; // Error: constinit can only be applied to a
                                 // variable with static or thread storage duration
    int prev = pow;
    pow = pow * pow;
    return prev;
}

int main()
{
    assert(sq == 4);
    sq = twice(1); // Unlike constexpr this value can be changed later at runtime
    assert(sq == 2);
    
    assert(square_4_gen() == 16);
    assert(square_4_gen() == 256);
    assert(square_4_gen() == 65536);
}

결함 보고 (Defect reports)

다음 동작 변경 결함 보고는 이전에 발표된 C++ 표준에 소급 적용됐어요.

DR 적용 대상 발표된 동작 올바른 동작
CWG 2543 C++20 constinit으로 선언된 변수가 정적 초기화의 일부로 동적 초기화되는 경우 동작이 불명확했음 이 경우 프로그램은 ill-formed

더 알아보기