Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using constinit variable to initialize a constexpr variable

Tags:

c++

c++20

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.

like image 541
geza Avatar asked Oct 02 '19 09:10

geza


People also ask

Can you modify constexpr?

A const int var can be dynamically set to a value at runtime and once it is set to that value, it can no longer be changed. A constexpr int var cannot be dynamically set at runtime, but rather, at compile time. And once it is set to that value, it can no longer be changed.

Can a member function be constexpr?

const can only be used with non-static member functions whereas constexpr can be used with member and non-member functions, even with constructors but with condition that argument and return type must be of literal types.

Does constexpr need to be static?

A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later. So, what does constexpr mean?

What is a constexpr variable?

constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time. A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations.


3 Answers

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;
like image 170
Barry Avatar answered Oct 11 '22 05:10

Barry


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.

like image 28
Deduplicator Avatar answered Oct 11 '22 06:10

Deduplicator


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.

like image 21
darune Avatar answered Oct 11 '22 07:10

darune