2

My understanding is that a function scope static constexpr is evaluated at compile time. If this is the case, what justification, if any, does MSVC have for the following error:

int main()
{
    int i = 5;

    switch (i)
    {
        // Original question:
        static constexpr int j = 7; // legal?
        // My actual use case, which may not be legal.
        static constexpr int k[2] = { 7, 4 };

    default:
    case 0:
        break;
    }
    return 0;
}

testapp.cpp(10) : error C2360 : initialization of 'j' is skipped by 'case' label

If that were a non-constexpr then yes, this is a valid complaint. However since the constexpr is evaluated at compile time, there should be no need to execute anything at the declaration site.

-- Edit --

With apologies to Martin Bonner whose answer was deleted since it did not apply to my original question.

My actual use case is the second: a constexpr array with an initializer list. From what I saw in the standard that was cited, my first case of a static constexpr scalar int is not prohibited. However it appears that what I'm trying to do is ill-formed.

If this is indeed true, then why? Isn't the whole point of constexpr to evaluate things at compile time, therefore it should not matter if control ever actually passes the declaration.

David G
  • 113
  • 8
  • fyi g++ 5.1.0 compiles it clean. – Richard Critten May 24 '17 at 18:04
  • 2
    I will say that's an awfully strange way to write a switch statement. Introduce another scope just outside of it to place your variable. `{static constexpr int j = 7; swtich(i){...}}` – AndyG May 24 '17 at 18:10
  • constexpr means that it is known at compile time, but other than that, it's semantics are the same as any other variable (plus the const-ness) – Passer By May 24 '17 at 18:33
  • @AndyG Certainly, I **can** declare it immediately above the switch, which is what I currently do. But it's a question of keeping the declaration close to its use. C++ gave us ability to declare variables mid-block for precisely this reason. I use the array in just a couple of cases in the switch, so I'd like to keep its declaration as close to its use as I can. – David G May 24 '17 at 18:55
  • @DavidG: Yeah that's fair. It was a very good question that had me researching a lot of about the rules of initialization and switch statements, so thank you for the opportunity to post an answer. – AndyG May 24 '17 at 19:16

2 Answers2

4

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

  • Reading over this a couple of times, it seems to me that the error I'm making (?) is in assuming that because use of `constexpr` guarantees compile time evaluation is possible, it mandates that it must take place. Your first cited paragraph clearly states that even in the presence of `constexpr` run time initialization is legal. The first code snippet in @dyp's answer to this question: https://stackoverflow.com/questions/26152096/when-and-why-would-you-use-static-with-constexpr/26162710#26162710 shows an example of why `const` and `constexpr` are essentially identical at run-time. – David G May 24 '17 at 20:09
  • I deleted my answer because it seemed to refer to auto only despite the first sentence being any declaration. I think there's a DR in that. – Martin Bonner supports Monica May 25 '17 at 07:04
2

It's a MSVC bug.

It's illegal to jump into the scope of a variable declaration+initializer (e.g. case 0: int j = 5;), but the rules also say that a static is initialized the first time control passes through their declaration. And declarations, however, are allowed to exist within a switch statement body, according to §6.4.2/6 [stmt.switch]

"...Declarations can appear in the substatement of a switch-statement."

So static is what makes everything okay in this instance.

You could be totally fine if you had a non-static declaration (without initialization) at the beginning of a switch statement; it's the skipping over of declaration with initialization that makes the compiler bug out.

switch (i)
{
    int j;
    case 0:
        j = 2;
        std::cout << 0 << std::endl;
    break;
    default:
        j = 3;
      std::cout << j << std::endl;
    break;
}

even this is fine (although dangerous):

case 0:
    int j;
    j = 5;
break;
default:
    /*..*/

Because of the rules surrounding control transfer: §6.7/3 [stmt.dcl]:

It is possible to transfer into a block, but not in a way that bypasses declarations with initialization.

I think cppreference explains it better:

If transfer of control enters the scope of any automatic variables (e.g. by jumping forward over a declaration statement), the program is ill-formed (cannot be compiled), unless all variables whose scope is entered have

1) scalar types declared without initializers

...

AndyG
  • 39,700
  • 8
  • 109
  • 143
  • It does seem like a MSVC bug (surprise!), but what you're saying doesn't look right. Declarations have always been allowed, and the compiler is complaining about *initialization* – Passer By May 24 '17 at 18:36
  • @PasserBy: I agree with you. It's becoming more ambiguous to me as I investigate more. It appears that the rules regarding static initialization may conflict with the rules regarding a switch statement, and whether there is precedence or not is confusing. It may simply be UB. I'm going to delete my answer on account of that. (High rep users will still be able to see it) – AndyG May 24 '17 at 18:49
  • @PasserBy: I did a little more research and updated the post accordingly. I feel it is more accurate than before. – AndyG May 24 '17 at 19:00