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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With