geza
geza

Reputation: 29942

Using constinit variable to initialize a constexpr variable

Look at this little example:

constinit int a = 0;
constexpr int b = a;

clang doesn't compile it (godbolt):

2:15: error: constexpr variable 'b' must be initialized by a constant expression

Is this correct diagnostics?

If yes, why doesn't the standard allow this? I understand, that a's value may change during running (or even during dynamic-initialization), but at constant-initialization, its value is known, so it could be used to initialize b.

Upvotes: 5

Views: 1428

Answers (3)

darune
darune

Reputation: 10962

is this correct diagnostics?

I would say yes. According to cppreference:

constinit - specifies that a variable must have static initialization, i.e. zero initialization and constant initialization, otherwise the program is ill-formed.

Static (constant) initialization and constant expression are different concepts in that a constant expression may be used in a constant initialization but not the other way around. constinit shouldn't be confused with const. It means the initialization (only) is constant.

However, constinit const can be used in a constexpr and they are supposed to be the same.

Counter-example:

constinit int a = 0;

struct X{
    X() {
        a = 4;
    }
};

X x{};

constexpr int b = a;

What is b supposed to be ? The point is that a can be changed in non-const ways before b is seen.

Upvotes: 1

Barry
Barry

Reputation: 302757

Yes, the diagnostic is correct. constexpr variables must be initialized with a constant expression, and a is not a constant expression (it's a mutable variable).

The purpose of constinit (P1143) is to force a variable declaration to be ill-formed if it's initialization is not constant. It doesn't change anything about the variable itself, like it's type or anything (in the way that constexpr is implicitly const). Silly example:

struct T {
    int i;
    constexpr T(int i) : i(i) { }
    T(char c) : i(c) { }
};

constinit T c(42); // ok
constinit T d('X'); // ill-formed

That is all constinit is for, and the only real rule is [dcl.constinit]/2:

If a variable declared with the constinit specifier has dynamic initialization ([basic.start.dynamic]), the program is ill-formed. [ Note: The constinit specifier ensures that the variable is initialized during static initialization ([basic.start.static]). — end note ]

The const in constinit refers only to the initialization, not the variable, not any types. Note that it also doesn't change the kind of initialization performed, it merely diagnoses if the wrong kind is performed.

In:

constinit int a = 0;
constexpr int b = a;

0 is a constant expression, so the initialization of a is well-formed. Once we get past that, the specifier doesn't do anything. It's equivalent to:

int a = 0; // same behavior, a undergoes constant initialization
constexpr int b = a;

Which is straightforwardly ill-formed.


but at constant-initialization, its value is known, so it could be used to initialize b.

Sure, at this moment. What about:

constinit int a = 0;
cin >> a;
constexpr int b = a;

That's obviously not going to fly. Allowing this would require extending what a constant expression is (already the most complex rule in the standard, in my opinion) to allow for non-constant variables but only immediately after initialization? The complexity doesn't seem worth it, since you can always write:

constexpr int initializer = 0;
constinit int a = initializer;
constexpr int b = initializer;

Upvotes: 4

Deduplicator
Deduplicator

Reputation: 45654

constexpr combines constinit and const without exception.

constinit forces initialization with a compiletime constant expression, and during static initialization, disallowing dynamic initialization. It does not change the variable itself in any way though.

const forbids changing the variable, though can be weakened by mutable members.

Both together make it a compiletime constant expression.

In summary, yes, the diagnostic is right.

Upvotes: 2

Related Questions