| Document Number: | P1143 | | -----------------|-------------------------------------------------| | Date: | 2018-07-04 | | Project: | Programming Language C++, Evolution | | Revises: | none | | Reply to: | eric@efcs.ca | # Adding the `[[constinit]]` attribute ## Introduction We're all familar with the Static Initialization Order Fiasco. Static storage duration variables with dynamic initializers cause hard-to-find bugs caused by the indeterminate order of dynamic initialization. However, static variables with constant initializers avoid these pitfalls by causing the initialization to take place at compile time. These variables can be safely used during dynamic initialization across translation units. Unfortunatly the rules for constant initialization are complex and change dialect to dialect. This makes it non-obvious what form of initialization is taking place simply by inspecting the code. We need a way to enforce that constant initialization is truely taking place. It’s important to be able to fail fast instead of silently falling back on dynamic initialization. This paper proposes the `[[constinit]]` attribute. The attribute can be applied to variable declarations with static storage duration. It acts as a compile time assertion that the requirements for constant initialization have been met, and that it will actually take place. For example: ```c++ // Compiled as C++14 struct T { constexpr T(int) {} ~T(); // non-trivial }; [[constinit]] T x = {42}; // Initialization OK. Doesn't check destructor. [[constinit]] T y = 42; // error: variable does not have a constant initializer // copy initialization is not a constant expression on a non-literal type in C++14. ``` ## Open Questions ### Applying `[[constinit]]` to declarations or just definitions? `[[constinit]]` communicates the intention of the programmer to both the compiler and other programmers. In order for the compiler to inforce the intent, the declaration need only be present on the definition, and not any previous declarations. However, to express the intent to other programmers there is value in allowing the attribute to appear on non-defining declarations. For example: ```c++ // Foo.h struct Foo { [[constinit]] static int x; }; // Foo.cpp int Foo::x = 42; ``` However, there is another case where a variable declaration is not a definition, and that's when `extern` is specified. In this case the `extern` variable declaration may not be available to the compiler when checking the definition, and so it has no way of knowing that it was, at one point, declared with `[[constinit]]`. This causes the attribute to be silently ignored. For these reasons this proposal disallows `[[constinit]]` from being mixed with `extern` unless the declaration is a definition. For example: ```c++ [[constinit]] extern int x = 42; // OK [[constinit]] extern int y; // ill-formed. ``` This issue also affects redundant redeclarations of `constexpr` static data members. For example: ```c++ struct Foo { static constexpr int x = 42; }; [[constinit]] constexpr int Foo::x; // OK? ``` In the above case, the addition of `[[constinit]]` to a `constexpr` variable is meaningless. Additionally, allowing a redeclaration to add `[[constinit]]` after the variable is defined would be problematic (though in this particular case `constexpr` makes it moot). ### Supporting Zero Initialization (AKA. Disallowing Dynamic Initialization) In some cases Constant initialization is not performed, but neither is dynamic initialization. This means that the zero initialization performed as a part of static initialization is the only initialization to take place. In this case the variable is effectively initialized at compile time and is safe to use during dynamic initialization. For example: ```c++ [[constinit]] int x; // OK struct T { int x, y, z; }; [[constinit]] T t; // Also OK. ``` Since these cases provide the same safety guarentees as the constant initialization cases, the `[[constinit]]` attribute should allow them as well. Unfortunatly it's not clear exactly when dynamic initialization may be ommitted in cases where constant initialization doesn't occur. Specifically it is implementation defined according to `[basic.start.static]`p3: > An implementation is permitted to perform the initialization of a variable with static or thread storage duration as a static initialization even if such initialization is not required to be done statically, provided that > (3.1) the dynamic version of the initialization does not change the value of any other object of static or thread storage duration prior to its initialization, and > (3.2) the static version of the initialization produces the same value in the initialized variable as would be produced by the dynamic initialization if all variables not required to be initialized statically were initialized dynamically. This would make various code ill-formed depending on the compiler used and its implementation specific behavior. I think this behavior is unacceptable. If we want to support variables which don't strictly meet the requirements for *constant initialization* but which implementations do emit as though they did, then I think we need to strengthen the requirements on said variables to require that *all* implementations emit them without using dynamic initialization. ## Wording #### XX.X.X Constant initialization attribute [dcl.attr.constinit] 1. The *attribute-token* `constinit` may be applied to a variable declaration with static or thread storage duration. It shall appear at most once in each *attribute-list* and no *attribute-argument-clause* shall be present. 2. The attribute may not appear on a variable declaration unless the declaration is a definition or declares a static data member in a class definition. [*Note*: This ensures that the presence or absence of the attribute can be determined at the point of definition --- *end note*] 3. A variable declared without the `constinit` attribute can later be redeclared with the attribute and vice-versa. 4. If a variable declared with the attribute has *dynamic initialization* (`[basic.start.dynamic]`), the program is ill-formed. [*Note*: Otherwise the variable is fully initialized during *static initialization* using either *constant initialization* or *zero initialization* --- *end note*] 5. [*Example*: ```c++ 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 ``` --- *end example*] ## References * [Clang's `[[require_constant_initialization]]` documentation](https://clang.llvm.org/docs/AttributeReference.html#require-constant-initialization-clang-require-constant-initialization) * [A Clang implementation of the proposal](https://github.com/efcs/clang/tree/fix-const-init) * [Definition of *Constant Initialization* from `[basic.start.static]`](http://eel.is/c++draft/basic.start#static-2)