As @PasserBy hinted in a comment, constexpr
doesn't affect as much as you'd hope. After static constexpr int a = 10;
, code that reads a
's value can be optimised at compile time to use 10
directly, and the compiler will ensure that the initialiser 10
is a compile-time constant, but there is still an object there that in some cases might need actual storage at run-time, including initialisation. For instance when its address is taken.
Initialisation of block-scope static
s may happen early, the same way as for file scope objects, or it may happen when the execution reaches the declaration:
N4140 [dcl.stmt]p4:
[...] An implementation is permitted to perform early initialization of other block-scope variables with static or thread storage duration under the same conditions that an implementation is permitted to statically initialize
a variable with static or thread storage duration in namespace scope (3.6.2). Otherwise such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. [...]
Now, @MartinBonner's deleted answer quoted [dcl.stmt]p3:
It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps90 from a point where a variable with automatic storage duration is not in scope to a
point where it is in scope is ill-formed unless the variable has scalar type, class type with a trivial default constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the
preceding types and is declared without an initializer (8.5).
The second sentence only addresses objects with automatic storage duration, but the first doesn't. You're transferring into a block in a way that does potentially bypass a declaration with initialisation. The only thing is, it doesn't necessary bypass a declaration with initialisation: if the initialisation is performed early, no initialisation gets bypassed.
In this particular case, I would hope that all sensible implementations perform early initialisation, but it's not required, and because of that I think the error message is allowed. But I do suspect this isn't MSVC's intended behaviour:
Here's an example where you'd definitely want an error:
int &f() { static int i; return i; }
int main() {
switch (0) {
static int &i = f();
case 0:
return i;
}
}
Here, the initialisation of the variable i
is skipped, and as a result, an uninitialised reference is accessed, triggering a segmentation fault on multiple compilers. Yet in this case, MSVC does not show a compilation error. (Nor do other compilers.) It doesn't make sense for the error message to be issued in a situation where the error is harmless, but omitted where the error is harmful. Because of that, I suspect the error message you're receiving is not intended, and worth reporting as a bug.