"surprising" constant initialization because of definition order - c++

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.

Related

Static local variables' initialization at program startup

cppreference states:
Variables declared at block scope with the specifier static or thread_local (since C++11) have static or thread (since C++11) storage duration but are initialized the first time control passes through their declaration (unless their initialization is zero- or constant-initialization, which can be performed before the block is first entered).
My question is about that "unless" part - can you give examples of code where the static local variable is zero- and constant-initialized? Can class objects (e.g. MyClass obj;) be zero- or constant-initialized? If so, does that mean their constructor would be called before main() starts?
can you give examples of code where the static local variable is zero- and constant-initialized?
In the below given example, the local static variable n satisfies both the conditions for constant initialization so that here the "unless" part in your quoted statement also holds.
int main()
{
static const int i = 5; //both conditions for constant initialization are satisfied so that the "unless" part of your quoted statement also holds
}
A variable or temporary object obj is constant-initialized if
either it has an initializer or its default-initialization results in some initialization being performed, and
its initialization full-expression is a constant expression, except that if obj is an object, that full-expression may also invoke constexpr constructors for obj and its subobjects even if those objects are of non-literal class types (since C++11).
Can class objects (e.g. MyClass obj;) be zero- or constant-initialized?
Yes class objects can also be constant initialized.
struct Custom
{
constexpr Custom()
{
}
};
int main()
{
static constexpr Custom obj;//here also both conditions for constant initialization are satisfied
}
Note also that initialization does not necessarily implies that a constructor must be used. For example, in #include <string> std::string s; int main(){} the s is first is zero initialized and then default initialized(using the default ctor). This means that the first initialization step here, does not use any ctor.
According to this: https://en.cppreference.com/w/cpp/language/zero_initialization
Zero-initialization is performed [...] For every named variable with static [...] storage duration that is not subject to constant initialization, before any other initialization.
So in this context
int main() {
...
static MyClass a;
...
}
zero-initialization of a can be performed before main() starts, but its constructor will be called inside of main() as expected.

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.
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.

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).

Static initialization when it is not required

Quote from 3.6.2/3 of N3797 C++14 final working draft:
An implementation is permitted to perform the initialization of a
non-local variable with static storage duration as a static
initialization even if such initialization is not required to be done
statically, provided that
โ€” the dynamic version of the initialization does not change the value
of any other object of namespace scope prior to its initialization,
and
โ€” 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.
What does all variable have to initialization of one specific variable?
If it possible, describe the latter point by example.
This matters when the initialiser of one variable refers to another variable.
constexpr int f(int);
extern const int a = f(1); // not required to be statically initialized
extern const int b = a; // also not required to be statically initialized
constexpr int f(int x) { return x; }
Now suppose that the implementation chooses to statically initialize b, but dynamically initialize a. In that case, the initialization of b would take place before that of a. The text you ask about explains that this doesn't permit an implementation to initialize b to zero: even if b is initialized first, its value must be f(1), which is 1.

Initialization of static const member variables based on each other

Is this safe to assume that A is initialized to 1 when initializing B and C here?
struct Test {
static const int A = 1;
static const int B = A + 1;
static const int C = B + 1;
};
int main() {
printf("%i %i %i\n", Test::A, Test::B, Test::C); ==> 1 2 3
}
what about for non-integral static members
struct Test2 {
constexpr static const Test A = Test();
constexpr static const Test B = A;
constexpr static const Test C = B;
};
clang -Wall issues no warnings.
They will be initialized in the order of definition in given translation unit and before any other objects or variables.
Maybe. In this case, the initialization of A is static, so
A can be used as an integral constant expression. Which then
propagates to B. When you declare the members, they will be
initialized statically, to what the compiler has evaluated (i.e.
0 and 1), before the program even starts.
If they aren't initialized statically (which is only legal in
C++11), for any reason whatever, then they will be initialized
in the order the definitions appear in the source file, if they
are defined in the same source file. If they are not defined in
the same source file, then the order they are initialized is
unspecified.
Concerning your second example: if they are really constexpr,
there are still only constant expressions involved, so
everything will be decided at compile time, and the members will
be statically initialized.
Yes. Objects with static storage duration are always zero-initialized prior to anything else:
Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place.
[3.6.2ยง2]
Therefore any constant or dynamic initialization may assume that all other objects with static storage duration are at least zero-initialized.
Note that this does not give any guarantees for anything other than zero-initialization!