Using constinit variable to initialize a constexpr variable - c++

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.

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;

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.

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.

Related

Is `a` in `static const int a = std::random_device{}();` a glvalue core constant expression?

Cppreference states that the expression a defined in
static const int a = std::random_device{}();
is a glvalue constant expression. That would mean that it must also be a core constant expression that is, among other conditions, its evaluation must not involve any calls to functions (or constructors) that aren't declared constexpr as stated here (3rd point). Looking at std::random_device constructors, I'm not seeing any constructor being constexpr.
I checked out the draft on this and I'm not seeing any explicit mention of constructors, but I would say it is implied here:
— an invocation of a non-constexpr function;
Am I missing something here? On the other hand, why isn't it a prvalue constant expression?
Let's make this example simpler (and probably cppreference should do the same):
int f(); // not constexpr, not even defined
void test() {
static const int a = f();
constexpr const int& ra = a; // OK: a is a glvalue constant expression
constexpr int ia = a; // Error: a is not a prvalue constant expression
}
It's true that f() -- or, in the original example, std::random_device{}() -- is not a constant expression. But we don't need it to be to initialize ra. Because we're not reading a, we're just binding a reference to it -- all we need to bind a constexpr reference to a is for a to have static storage duration, which it does. So that's fine.
But reading a as a constant is not allowed, that's what the next line is indicating: we can't initialize ia because a isn't a prvalue constant expression (specificaly, we're violating the rule that we can't apply an lvalue-to-rvalue conversion becuase a isn't usable in constant expressions, which is currently [expr.const]/5.9).
That's basically the distinction between ra and ia: ra just needs a to have a stable address, because that's the part that we need to be constant. But ia needs a to have a constant value. This might be even more obvious if we did:
void test()
{
static const int a = f();
constexpr const int& ra = a; // OK
constexpr const int* pa = &a; // OK
constexpr int ia = a; // Error
}
ra and pa are equivalent - the only thing we care about is the address of a, not the value of a. The value of a isn't constant, but the address is, so it works.

Why does constinit allow UB?

Say I initialize variables like this:
#include <cstdint>
constexpr uint16_t a = 65535;
constinit int64_t b = a * a; // warning: integer overflow in expression of type 'int' results in '-131071' [-Woverflow]
constexpr int64_t c = a * a; // error: overflow in constant expression [-fpermissive]
Both b and c produce undefined behavior because of integer overflow.
With constinit the variable is constant initialized. Which makes no guarantee about UB.
With constexpr the variable is initialized with a constant expression. Constant expression guarantee not to have any UB. So here the signed integer overflow in an error. But the variable is also automatically const.
So how do I best initialize a non-const variable with a constant expression?
Do I have to write
constexpr int64_t t = a * a; // error: overflow in constant expression [-fpermissive]
constinit int64_t b = t;
or
constinit int64_t b = []()consteval{ return a * a; }(); // error: overflow in constant expression
every time?
This is related to CWG issue 2543.
As it stands currently, because the compiler is allowed to replace any dynamic initialization with static initialization if it can and because constinit is only specified to enforce "no dynamic initialization", it might still allow an initializer which is not a constant expression (maybe dependent on the interpretation as discussed in the linked issue). constinit therefore reflects whether there will actually be initialization at runtime (which is relevant to avoiding dynamic initialization order issues). It does not necessarily reflect whether the initializer is a constant expression.
As stated in the issue description, this is practically not really implementable though because the dynamic/static initialization choice is made too late in the compilation process to always make constinit reflect it properly.
With one possible resolution of the issue, the specification of constinit might be changed to actually require the variable to be constant-initialized instead of just requiring that there is no dynamic initialization. If that was the resolution taken, then your first example for the initialization of b would also require the compiler to diagnose the UB and all of the other solutions would become obsolete.
The issue description doesn't seem to really favor any direction though.
For the current situation (and if the resolution is taken in another direction), an alternative to the solutions you gave is:
template<typename T>
consteval auto force_compiletime(T&& t) {
return std::forward<T>(t);
}
or
template<typename To, typename T>
consteval To force_compiletime2(T&& t) {
return std::forward<T>(t);
}
and then
constinit auto t = force_compiletime(static_cast<int64_t>(a * a));
or
constinit auto t = force_compiletime2<int64_t>(a * a);
Note that you need to include the target type in this way in the initializer, otherwise any potentially UB in the conversion will not be diagnosed. If you don't care about that
constinit int64_t t = force_compiletime(a * a);
would also be fine.
Technically the solution with the consteval lambda from your question is ill-formed, no diagnostic required, because the lambda is marked consteval but can never produce a constant expression when called. But I would expect any non-malicious compiler to still diagnose such a call.

Can an object of class type T be constant initialized when T has a non-trivial destructor?

Let's look at this sample of code:
class D
{
public:
constexpr D(int val) : i(val) { };
~D() { };
private:
int i;
};
D d(3);
According to the documentation, D should be constant initialized:
Only the following variables are constant initialized: [...]
2. Static or thread-local object of class type that is initialized by a
constructor call, if the constructor is constexpr and all constructor
arguments (including implicit conversions) are constant expressions,
and if the initializers in the constructor's initializer list and the
brace-or-equal initializers of the class members only contain constant
expressions.
Indeed, d is initialized by constructor call, the constructor of D is constexpr and my argument (3) is a constant expression.
However, to specify to the compiler the value of a variable can be evaluated at compile time, it is possible to use constexpr specifier. But, in this case, it won't compile because D is not a LiteralType because it define a non-trivial constructor.
So, in my snippet, is d really constant initialized? If so, why can't I use constexpr specifier?
So, in my snippet, is d really constant initialized? If so, why can't I use constexpr specifier?
Yes, it will be constant initialized. As you've quoted, constant initialization doesn't need the type to be a LiteralType. But constexpr does need it. Your type is not a LiteralType, so it cannot be a constexpr. But the type and constructor call fulfills the requirements of being constant initialization.
Btw., C++20 will have constinit. With this, you can make sure that a variable gets static initialized (which means constant initialization in your case).
You can check out constinit for your example on godbolt, as a further evidence that it compiles successfully, and you can see that the object is initialized at compile-time (not a requirement by the standard, but GCC does it).

As far as I can tell the function below is not constexpr, but the code compiles in clang and g++. What am I missing?

I got this example from §5.19/2 in N4140:
constexpr int incr(int &n) {
return ++n;
}
As far as I can tell, this is not a constexpr function. But the snippet compiles in clang and g++. See live example. What am I missing here?
In C++14 the rules for constexpr function were relaxed and the paper N3597: Relaxing constraints on constexpr functions. The paper goes into the rationale and the effects and it includes the following (emphasis mine):
As in C++11, the constexpr keyword is used to mark functions which the implementation is required to evaluate during translation, if they are used from a context where a constant expression is required. Any valid C++ code is permitted in constexpr functions, including the creation and modification of local variables, and almost all statements, with the restriction that it must be possible for a constexpr function to be used from within a constant expression. A constant expression may still have side-effects which are local to the evaluation and its result.
and:
A handful of syntactic restrictions on constexpr functions are
retained:
asm-declarations are not permitted.
try-blocks and function-try-blocks are not permitted.
Declarations of variables with static and thread storage duration have some restrictions (see below).
and we can find this covered in N4140 section 7.1.5 [dcl.constexpr] which says:
The definition of a constexpr function shall satisfy the following constraints:
it shall not be virtual (10.3);
its return type shall be a literal type;
each of its parameter types shall be a literal type;
its function-body shall be = delete, = default, or a compound-statement that does not contain
an asm-definition,
a goto statement,
a try-block, or
a definition of a variable of non-literal type or of static or thread storage duration or for which
no initialization is performed.
The last example shows how incr can be used in a constexpr:
constexpr int h(int k) {
int x = incr(k); // OK: incr(k) is not required to be a core
// constant expression
return x;
}
constexpr int y = h(1); // OK: initializes y with the value 2
// h(1) is a core constant expression because
// the lifetime of k begins inside h(1)
and the rule that covers the lifetime of k begins inside h(1) is:
modification of an object (5.17, 5.2.6, 5.3.2) unless it is applied to a non-volatile lvalue of literal type
that refers to a non-volatile object whose lifetime began within the evaluation of e;
The wording in 7.1.5 [dcl.constexpr] shows us why incr is a valid constexpr:
For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting
constexpr constructor, if no argument values exist such that an invocation of the function or constructor
could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no
diagnostic required.
As the modified example given by T.C.:
constexpr int& as_lvalue(int&& i){ return i; }
constexpr int x = incr(as_lvalue(1)) ;
shows, we can indeed use incr as a subexpression of a core constant expression and therefore it is not ill-formed.
As far as I can tell, this is not a constexpr function.
Why do you say that?
The example from §5.19/2 reads:
constexpr int g(int k) {
constexpr int x = incr(k); // error: incr(k) is not a core constant
// expression because lifetime of k
// began outside the expression incr(k)
return x;
}
incr(k) not being a core constant expression does not mean incr cannot not be a constexpr function.
Under C++14's constexpr rules, it is possible to use incr in a constexpr context, for example:
constexpr int incr(int& n) {
return ++n;
}
constexpr int foo() {
int n = 0;
incr(n);
return n;
}
Unless it's downright impossible for the body of the function to be constexpr (for example, calling a non-constexpr function unconditionally), the compiler has no reason to produce an error at the point of definition.
A constexpr function may even contain paths/branches in the body which would not be constexpr. As long as they are never taken in a constexpr context, you will not get an error.
For example:
constexpr int maybe_constexpr(bool choice, const int& a, const int& b) {
return choice ? a : b;
}
constexpr int a = 0;
int b = 1;
static_assert(maybe_constexpr(true, a, b) == 0, "!");
live example

"surprising" constant initialization because of definition order

When reading the slides about constexpr the introduction is about "surprisingly dynamic initialization with consts". The example is
struct S {
static const int c;
};
const int d = 10 * S::c;
const int S::c = 5;
Alas, the audio track is missing, so are the notes, so I can only guess what is meant here.
Is it corrrect that d is "surprisingly" initialized dynamically, because S::c is defined before d? That the declaration of S::c is before d is probably not enough, the compiler needs the complete definition, right?
That said, I suspect, that in the following example d would be initialized statically?
struct S {
static const int c;
};
const int S::c = 5;
const int d = 10 * S::c; // now _after_ defn of S::c
And to take the cake, in C++11, what would have to be constexpr for full static initialization? S::c, d or both?
In the first example, d is not initialized by a constant expression, because S::c is not
a non-volatile const object with a preceding initialization,
initialized with a constant expression
(see C++11 [expr.const]p2, bullet on lvalue-to-rvalue conversions), because the initialization of S::c does not precede the initialization of d. Therefore static initialization will be used for S::c (because it is initialized by a constant expression), but dynamic initialization can be used for d.
Since static initialization precedes dynamic initialization, d would be initialized to 50 by its dynamic initializer. The compiler is permitted to convert the dynamic initialization of d to static initialization, but if it does, it must produce the value that d would have had if every variable which could have used dynamic initialization had, in fact, used dynamic initialization. In this case, d is initialized to 50 either way. See C++11 [basic.start.init]p2 for more information on this.
There is no way to add constexpr to the first example to guarantee that static initialization is used for d; in order to do that, you must reorder the initializations. However, adding constexpr will produce a diagnostic for the first example, which will at least allow you to ensure that dynamic initialization is not used (you get static initialization or a compilation error).
You can update the second case to ensure that static initialization is used as follows:
struct S {
static const int c; // do not use constexpr here
};
constexpr int S::c = 5;
constexpr int d = 10 * S::c;
It is ill-formed to use constexpr on a variable declaration which is not a definition, or to use it on a variable declaration which does not contain an initializer, so const, not constexpr must be used within the definition of struct S. There is one exception to this rule, which is when defining a static constexpr data member of a literal, non-integral type, with the initializer specified within the class:
struct T { int n; };
struct U {
static constexpr T t = { 4 };
};
constexpr T U::t;
In this case, constexpr must be used in the definition of the class, in order to permit an initializer to be provided, and constexpr must be used in the definition of the static data member, in order to allow its use within constant expressions.
I believe that the rules laid out in 3.6.2 to determine when static initialization happens do not include the initialization for d, which is therefore dynamic initialization. On the other hand, S::c is indeed statically initialized (since 5 is a constant expression). Since all static initialization happens before dynamic initialization, you get the expected result.
To make d eligible for static initialization, it has to be initialized with a constant expression. This in turn forces you to write the S::c inline:
struct S { static constexpr int c = 5; };
const int d = S::c; // statically initialized
Note that the standard permits dynamic initialization to be replaced by static initialization, which is why reordering the two lines in your original example will cause the two different sorts of initialization. As TonyK points out, you can use array[d] in the static case, but not in the dynamic case, so you can check which one is happening. With the constexpr approach, you're guaranteed to have static initialization and you don't have to rely on optional compiler behaviour.
For static initialization one needs, roughly speaking, a constant-expression initializer.
To be a constant-expression, roughly speaking, a variable needs to be of a const type and have a preceding initialization with a constant-expression.
In the first example d's initializer is not a constant-expression, as S::c isn't one (it has no preceding initialization). Hence, d is not statically initialized.
In the second example d's initializer is a constant-expression, and everything is OK.
I'm simplifying matters. In full formal standardese this would be about nine times longer.
As for constexpr specifier, no object has to be declared constexpr. It is just an additional error-check. (This is about constexpr objects, not constexpr functions).
You may declare S::c constexpr in the second variant if you want some extra error protection (perhaps 5 will start changing its value tomorrow?) Adding constexpr to the first variant cannot possibly help.
You can find out whether a constant is statically or dynamically initialised by trying to declare an array:
struct S {
static const int c;
};
const int d = 10 * S::c; // (1)
const int S::c = 5; // (2)
static char array[d];
This code fails in g++ version 4.7.0, because d is dynamically initialised. And if you exchange (1) and (2), it compiles, because now d is statically initialised. But I can't find another way to fix it, using constexpr.