July 4, 2018·5 min read·engineeringcppinitializationstandardsconstexpr

The constinit proposal rejects silent dynamic initialization

P1143R0 proposes a constinit attribute for static and thread storage duration. It makes an initialization requirement explicit without claiming that compiler optimization alone supplies a portable guarantee.

A variable can stop receiving constant initialization after a seemingly small edit, leaving the program dependent on dynamic initialization without an explicit declaration of that change. Eric Fiselier's July 4 proposal, P1143R0, adds [[constinit]] as a compile-time assertion against that outcome. Its proposed wording makes a variable with dynamic initialization ill-formed when the attribute applies.

This revision proposes an attribute, not a keyword. It targets variables with static or thread storage duration and discusses how the annotation should interact with declarations and definitions. The design is still a proposal, with open questions about which initialization cases it should guarantee across implementations.

C++ initialization is one of the areas where several eras of the language can coexist in one codebase. I favor an explicit requirement that survives later edits and produces a diagnostic when the requirement is lost. The useful property here is the initialization guarantee, rather than a prediction about what machine instructions a compiler happens to emit.

Constant initialization avoids an ordering dependency

P1143R0 begins with the familiar problem of dynamic initialization across translation units. A variable requiring that process can be used by another initializer before the intended value is established. The proposal contrasts this with variables that receive constant initialization and can safely be used during dynamic initialization elsewhere.

The difficulty is identifying which case a declaration actually receives. Fiselier notes that the rules are subtle and change between language dialects. A source expression that looks simple does not necessarily establish the required category. The annotation asks the compiler to enforce the distinction instead of silently accepting a fallback.

The paper's own example uses a type with a constexpr constructor and a nontrivial destructor. In C++14, it contrasts direct-list-initialization with copy-initialization of that type. The former can meet the constant-initialization requirement while the latter does not under the conditions described. A constructor's spelling alone is therefore insufficient to classify every initialization that uses it.

That example also explains why this facility is not merely a request to make the variable a constant object. The proposed condition concerns when initialization takes place. It does not replace the type's ordinary operations or require the destructor to be trivial. The paper explicitly separates checking the initializer from checking destruction.

One constexpr function can have different initialization outcomes

P1143R0 supplies a smaller example based on a conditional expression. A constexpr helper can select either a constant result or a call to an ordinary function. The proposed attribute distinguishes the particular initialization path:

// Proposed attribute syntax in P1143R0.
const char *runtime_value() { return "dynamic"; }

constexpr const char *select_value(bool fixed)
{
    return fixed ? "constant" : runtime_value();
}

[[constinit]] const char *first = select_value(true);
[[constinit]] const char *second = select_value(false); // ill-formed

The first initialization takes the constant path. The second requires the ordinary call and fails the proposed requirement. Both declarations call the same helper, so the helper's constexpr declaration alone is not the guarantee. The attribute applies to the variable initialization that must satisfy the intended property.

The example is proposal syntax. Accepting an unknown attribute without implementing its semantics would not enforce this guarantee. A programmer needs an implementation of the facility, not merely a compiler that permits the token sequence in an attribute position.

Existing Clang behavior supplies a precedent

The proposal references Clang's require_constant_initialization attribute. The Clang 6 attribute documentation describes it as a C++ compile-time assertion that a variable has a constant initializer. It likewise requires static or thread storage duration and reports an error when the requirement is not met.

That documentation reproduces the nontrivial-destructor example and warns about a dialect-specific limitation. In C++03, Clang does not perform strict constant-expression checking for the attribute. Instead, it reports whether it can emit the variable as a constant, even where the initializer is not technically a constant initializer. The documentation calls that behavior nonportable.

The distinction is directly relevant to standardization. A compiler's ability to emit static data can be an optimization choice. A portable source annotation needs a condition that implementations can enforce consistently. Otherwise, the same declaration might satisfy the annotation on one compiler and fail on another solely because of optional implementation behavior.

I prefer the language guarantee to remain explicit at this boundary. An optimization can improve startup cost without establishing the ordering property that another translation unit relies on. The annotation's diagnostic is useful only if the required condition has a meaning beyond one compiler's chosen output.

The defining declaration must expose the requirement

P1143R0 considers whether the attribute should be permitted on non-defining declarations. A static data member declared within a class can carry the annotation where the definition can account for it. An unrelated extern declaration may never be visible to the compiler processing the definition.

The proposed wording consequently limits the allowed locations to a definition or a static data member declaration inside a class. Its examples permit an extern declaration with an initializer because that declaration is a definition, while rejecting the plain non-defining form. This is an enforcement rule, not just a preference about where documentation should appear.

The paper also discusses zero initialization. Some variables receive only zero initialization and need no dynamic initialization at all. They provide the relevant ordering safety even where constant initialization is not the right description. The proposed wording therefore focuses on disallowing dynamic initialization and explicitly describes constant or zero initialization as the remaining cases.

Redeclaration questions remain visible in the proposal's discussion. Adding the attribute to an already defined constexpr static data member supplies no new initialization guarantee in that example, but allowing annotations to arrive after a definition raises a more general enforcement problem. The implementation needs to know the requirement while checking the initializer. A later declaration cannot reliably repair the absence of that information during an earlier compilation.

The unresolved boundary concerns cases that implementations may optionally initialize statically. Fiselier rejects allowing those optional choices to determine whether annotated code is well-formed. If the facility supports additional cases, he argues that their requirements need strengthening so all implementations provide the promised absence of dynamic initialization. That is the remaining distinction between an optimizer's decision and the source-level assertion the proposal aims to supply.