Skip to content

Instantly share code, notes, and snippets.

@munael
Forked from EricWF/constinit.md
Last active April 6, 2019 22:56
Show Gist options
  • Save munael/c11d0c3b0d8a34640aa7555923658c60 to your computer and use it in GitHub Desktop.
Save munael/c11d0c3b0d8a34640aa7555923658c60 to your computer and use it in GitHub Desktop.
A Proposal to allow constexpr(<expr>) in expression context to force compile-time evaluation of expressions in C++
Document Number: Pxxxxxx (not yet)
Date: 2019-03-23
Project: Programming Language C++, Evolution
Revises: P1143r2
Reply to:

constexpr(<expr>) in expression context

const char *g() { return "dynamic initialization"; }
constexpr const char *f(bool p) { return p ? "constant initializer" : g(); }
 
const char *c = constexpr(f(true));  // OK.
const char *d = constexpr(f(false)); // ill-formed

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 allowing constexpr(expr) in expression contexts.

This form forces compile-time evaluation of expr, which in this case applies to initialization of variables, especially variable with static storage duration. For example:

// Compiled as C++14
struct T {
  constexpr T(int) {}
  ~T(); // non-trivial
};
T x = constexpr {42}; // Initialization OK. Doesn't check destructor.
T y = constexpr 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 constexpr to declarations or just definitions?

= constexpr 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 = constexpr form to appear in non-defining declarations. For example:

// Foo.h
struct Foo {
  static int x = constexpr;
};
// 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 = constexpr. This causes the keyword to be silently ignored.

For these reasons this proposal disallows = constexpr from being mixed with extern unless the declaration is a definition. For example:

extern int x = constexpr 42; // OK
extern int y = constexpr; // ill-formed.

This issue also affects redundant redeclarations of constexpr static data members. For example:

struct Foo {
  static constexpr int x = 42;
};
constexpr int Foo::x = constexpr; // OK?

In the above case, the addition of = constexpr to a constexpr variable is meaningless. Additionally, allowing a redeclaration to add = constexpr after the variable is defined would be problematic (though in this particular case constexpr makes it moot).

Previous Discussions

constexpr as a specifier. [TODO]

...

constinit as an attribute.

in r0 of this paper constinit was proposed as an attribute. When this idea was presented, there was general concencious that this feature would be better suited as a keyword. First, constinit enforces correctness and if compilers were allowed to ignore it as they can attributes, it would allow "ill-formed" programs to compile. Second, there was some discussion about the behavior of [[constinit]] being out-of-scope for attributes (I don't believe this to be the case).

Wording [TODO]

Modify [dcl.spec] as described below.

9.1 Specifiers [dcl.spec]

  1. The specifiers that can be used in a declaration are:

    ...

    constinit

2.Each decl-specifier shall appear at most once in a complete decl-specifier-seq, except that long may appear twice. The constexpr and consteval decl-specifiers shall not both appear in a decl-specifier-seq.At most one of the constexpr, consteval, and constinit keywords shall appear in a decl-specifier-seq.

9.1.X The constinit specifier [dcl.constinit]

Add the new section under [dcl.spec] with the following wording.

  1. The constinit specifier shall be applied only to declaration of a variable with static or thread storage duration.
  2. The specifier 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 specifier can later be redeclared with constinit and vice-versa.
  4. If a variable declared with the constinit specifier 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:
    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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment