1022
votes

I've always wondered this - why can't you declare variables after a case label in a switch statement? In C++ you can declare variables pretty much anywhere (and declaring them close to first use is obviously a good thing) but the following still won't work:

switch (val)  
{  
case VAL:  
  // This won't work
  int newVal = 42;  
  break;
case ANOTHER_VAL:  
  ...
  break;
}  

The above gives me the following error (MSC):

initialization of 'newVal' is skipped by 'case' label

This seems to be a limitation in other languages too. Why is this such a problem?

23
For an explanation based on the C BNF grammar, see stackoverflow.com/questions/1180550/weird-switch-error-in-obj-c/…johne
Here is a really good read about switch statements and labels (ABC:) in general.Etherealone
I would say 'Why can't variables be initialized in a switch statement rather than declared'.Since just declaring the variable give me only a warning in MSVC.ZoomIn
If you put everything inside the case label within curly braces { } then it will work.E Purdy

23 Answers

1235
votes

Case statements are only labels. This means the compiler will interpret this as a jump directly to the label. In C++, the problem here is one of scope. Your curly brackets define the scope as everything inside the switch statement. This means that you are left with a scope where a jump will be performed further into the code skipping the initialization.

The correct way to handle this is to define a scope specific to that case statement and define your variable within it:

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}
387
votes

This question is was originally tagged as [C] and [C++] at the same time. The original code is indeed invalid in both C and C++, but for completely different unrelated reasons.

  • In C++ this code is invalid because the case ANOTHER_VAL: label jumps into the scope of variable newVal bypassing its initialization. Jumps that bypass initialization of automatic objects are illegal in C++. This side of the issue is correctly addressed by most answers.

  • However, in C language bypassing variable initialization is not an error. Jumping into the scope of a variable over its initialization is legal in C. It simply means that the variable is left uninitialized. The original code does not compile in C for a completely different reason. Label case VAL: in the original code is attached to the declaration of variable newVal. In C language declarations are not statements. They cannot be labeled. And this is what causes the error when this code is interpreted as C code.

    switch (val)  
    {  
    case VAL:             /* <- C error is here */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:     /* <- C++ error is here */
      ...
      break;
    }
    

Adding an extra {} block fixes both C++ and C problems, even though these problems happen to be very different. On the C++ side it restricts the scope of newVal, making sure that case ANOTHER_VAL: no longer jumps into that scope, which eliminates the C++ issue. On the C side that extra {} introduces a compound statement, thus making the case VAL: label to apply to a statement, which eliminates the C issue.

  • In C case the problem can be easily solved without the {}. Just add an empty statement after the case VAL: label and the code will become valid

    switch (val)  
    {  
    case VAL:;            /* Now it works in C! */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:  
      ...
      break;
    }
    

    Note that even though it is now valid from C point of view, it remains invalid from C++ point of view.

  • Symmetrically, in C++ case the the problem can be easily solved without the {}. Just remove the initializer from variable declaration and the code will become valid

    switch (val)  
    {  
    case VAL: 
      int newVal;
      newVal = 42;  
      break;
    case ANOTHER_VAL:     /* Now it works in C++! */
      ...
      break;
    }
    

    Note that even though it is now valid from C++ point of view, it remains invalid from C point of view.

138
votes

Ok. Just to clarify this strictly has nothing to do with the declaration. It relates only to "jumping over the initialization" (ISO C++ '03 6.7/3)

A lot of the posts here have mentioned that jumping over the declaration may result in the variable "not being declared". This is not true. An POD object can be declared without an initializer but it will have an indeterminate value. For example:

switch (i)
{
   case 0:
     int j; // 'j' has indeterminate value
     j = 0; // 'j' set (not initialized) to 0, but this statement
            // is jumped when 'i == 1'
     break;
   case 1:
     ++j;   // 'j' is in scope here - but it has an indeterminate value
     break;
}

Where the object is a non-POD or aggregate the compiler implicitly adds an initializer, and so it is not possible to jump over such a declaration:

class A {
public:
  A ();
};

switch (i)  // Error - jumping over initialization of 'A'
{
   case 0:
     A j;   // Compiler implicitly calls default constructor
     break;
   case 1:
     break;
}

This limitation is not limited to the switch statement. It is also an error to use 'goto' to jump over an initialization:

goto LABEL;    // Error jumping over initialization
int j = 0; 
LABEL:
  ;

A bit of trivia is that this is a difference between C++ and C. In C, it is not an error to jump over the initialization.

As others have mentioned, the solution is to add a nested block so that the lifetime of the variable is limited to the individual case label.

39
votes

The whole switch statement is in the same scope. To get around it, do this:

switch (val)
{
    case VAL:
    {
        // This **will** work
        int newVal = 42;
    }
    break;

    case ANOTHER_VAL:
      ...
    break;
}

Note the brackets.

31
votes

After reading all answers and some more research I get a few things.

Case statements are only 'labels'

In C, according to the specification,

§6.8.1 Labeled Statements:

labeled-statement:
    identifier : statement
    case constant-expression : statement
    default : statement

In C there isn't any clause that allows for a "labeled declaration". It's just not part of the language.

So

case 1: int x=10;
        printf(" x is %d",x);
break;

This will not compile, see http://codepad.org/YiyLQTYw. GCC is giving an error:

label can only be a part of statement and declaration is not a statement

Even

  case 1: int x;
          x=10;
            printf(" x is %d",x);
    break;

this is also not compiling, see http://codepad.org/BXnRD3bu. Here I am also getting the same error.


In C++, according to the specification,

labeled-declaration is allowed but labeled -initialization is not allowed.

See http://codepad.org/ZmQ0IyDG.


Solution to such condition is two

  1. Either use new scope using {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. Or use dummy statement with label

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. Declare the variable before switch() and initialize it with different values in case statement if it fulfills your requirement

    main()
    {
        int x;   // Declare before
        switch(a)
        {
        case 1: x=10;
            break;
    
        case 2: x=20;
            break;
        }
    }
    

Some more things with switch statement

Never write any statements in the switch which are not part of any label, because they will never executed:

switch(a)
{
    printf("This will never print"); // This will never executed

    case 1:
        printf(" 1");
        break;

    default:
        break;
}

See http://codepad.org/PA1quYX3.

20
votes

You can't do this, because case labels are actually just entry points into the containing block.

This is most clearly illustrated by Duff's device. Here's some code from Wikipedia:

strcpy(char *to, char *from, size_t count) {
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}

Notice how the case labels totally ignore the block boundaries. Yes, this is evil. But this is why your code example doesn't work. Jumping to a case label is the same as using goto, so you aren't allowed to jump over a local variable with a constructor.

As several other posters have indicated, you need to put in a block of your own:

switch (...) {
    case FOO: {
        MyObject x(...);
        ...
        break; 
    }
    ...
 }
16
votes

Most of the replies so far are wrong in one respect: you can declare variables after the case statement, but you can't initialize them:

case 1:
    int x; // Works
    int y = 0; // Error, initialization is skipped by case
    break;
case 2:
    ...

As previously mentioned, a nice way around this is to use braces to create a scope for your case.

12
votes

My favorite evil switch trick is to use an if(0) to skip over an unwanted case label.

switch(val)
{
case 0:
// Do something
if (0) {
case 1:
// Do something else
}
case 2:
// Do something in all cases
}

But very evil.

10
votes

Try this:

switch (val)
{
    case VAL:
    {
        int newVal = 42;
    }
    break;
}
7
votes

You can declare variables within a switch statement if you start a new block:

switch (thing)
{ 
  case A:
  {
    int i = 0;  // Completely legal
  }
  break;
}

The reason is to do with allocating (and reclaiming) space on the stack for storage of the local variable(s).

6
votes

Consider:

switch(val)
{
case VAL:
   int newVal = 42;
default:
   int newVal = 23;
}

In the absence of break statements, sometimes newVal gets declared twice, and you don't know whether it does until runtime. My guess is that the limitation is because of this kind of confusion. What would the scope of newVal be? Convention would dictate that it would be the whole of the switch block (between the braces).

I'm no C++ programmer, but in C:

switch(val) {
    int x;
    case VAL:
        x=1;
}

Works fine. Declaring a variable inside a switch block is fine. Declaring after a case guard is not.

4
votes

The entire section of the switch is a single declaration context. You can't declare a variable in a case statement like that. Try this instead:

switch (val)  
{  
case VAL:
{
  // This will work
  int newVal = 42;
  break;
}
case ANOTHER_VAL:  
  ...
  break;
}
3
votes

If your code says "int newVal=42" then you would reasonably expect that newVal is never uninitialised. But if you goto over this statement (which is what you're doing) then that's exactly what happens - newVal is in-scope but has not been assigned.

If that is what you really meant to happen then the language requires to make it explicit by saying "int newVal; newVal = 42;". Otherwise you can limit the scope of newVal to the single case, which is more likely what you wanted.

It may clarify things if you consider the same example but with "const int newVal = 42;"

3
votes

I just wanted to emphasize slim's point. A switch construct creates a whole, first-class-citizen scope. So it is posible to declare (and initialize) a variable in a switch statement before the first case label, without an additional bracket pair:

switch (val) {  
  /* This *will* work, even in C89 */
  int newVal = 42;  
case VAL:
  newVal = 1984; 
  break;
case ANOTHER_VAL:  
  newVal = 2001;
  break;
}
3
votes

So far the answers have been for C++.

For C++, you can't jump over an initialization. You can in C. However, in C, a declaration is not a statement, and case labels have to be followed by statements.

So, valid (but ugly) C, invalid C++

switch (something)
{
  case 1:; // Ugly hack empty statement
    int i = 6;
    do_stuff_with_i(i);
    break;
  case 2:
    do_something();
    break;
  default:
    get_a_life();
}

Conversly, in C++, a declaration is a statement, so the following is valid C++, invalid C

switch (something)
{
  case 1:
    do_something();
    break;
  case 2:
    int i = 12;
    do_something_else();
}
3
votes

Interesting that this is fine:

switch (i)  
{  
case 0:  
    int j;  
    j = 7;  
    break;  

case 1:  
    break;
}

... but this isn't:

switch (i)  
{  
case 0:  
    int j = 7;  
    break;  

case 1:  
    break;
}

I get that a fix is simple enough, but I'm not understanding yet why the first example doesn't bother the compiler. As was mentioned earlier (2 years earlier hehe), declaration is not what causes the error, even despite the logic. Initialisation is the problem. If the variable is initialised and declared on the different lines, it compiles.

3
votes

I wrote this answer orginally for this question. However when I finished it I found that answer has been closed. So I posted it here, maybe someone who likes references to standard will find it helpful.

Original Code in question:

int i;
i = 2;
switch(i)
{
    case 1: 
        int k;
        break;
    case 2:
        k = 1;
        cout<<k<<endl;
        break;
}

There are actually 2 questions:

1. Why can I declare a variable after case label?

It's because in C++ label has to be in form:

N3337 6.1/1

labeled-statement:

...

  • attribute-specifier-seqopt case constant-expression : statement

...

And in C++ declaration statement is also considered as statement (as opposed to C):

N3337 6/1:

statement:

...

declaration-statement

...

2. Why can I jump over variable declaration and then use it?

Because: N3337 6.7/3

It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps (The transfer from the condition of a switch statement to a case label is considered a jump in this respect.)

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

Since k is of scalar type, and is not initialized at point of declaration jumping over it's declaration is possible. This is semantically equivalent:

goto label;

int x;

label:
cout << x << endl;

However that wouldn't be possible, if x was initialized at point of declaration:

 goto label;

    int x = 58; //error, jumping over declaration with initialization

    label:
    cout << x << endl;
2
votes

A switch block isn't the same as a succession of if/else if blocks. I'm surprised no other answer explains it clearly.

Consider this switch statement :

switch (value) {
    case 1:
        int a = 10;
        break;
    case 2:
        int a = 20;
        break;
}

It may be surprising, but the compiler will not see it as a simple if/else if. It will produce the following code :

if (value == 1)
    goto label_1;
else if (value == 2)
    goto label_2;
else
    goto label_end;

{
label_1:
    int a = 10;
    goto label_end;
label_2:
    int a = 20; // Already declared !
    goto label_end;
}

label_end:
    // The code after the switch block

The case statements are converted into labels and then called with goto. The brackets create a new scope and it is easy to see now why you can't declare two variables with the same name within a switch block.

It may look weird, but it is necessary to support fallthrough (that is, not using break to let execution continue to the next case).

1
votes

New variables can be decalared only at block scope. You need to write something like this:

case VAL:  
  // This will work
  {
  int newVal = 42;  
  }
  break;

Of course, newVal only has scope within the braces...

Cheers, Ralph

0
votes

I believe the issue at hand is that is the statement was skipped, and you tried to use the var elsewhere, it wouldn't be declared.

0
votes

newVal exists in the entire scope of the switch but is only initialised if the VAL limb is hit. If you create a block around the code in VAL it should be OK.

0
votes

C++ Standard has: It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps from a point where a local variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has POD type (3.9) and is declared without an initializer (8.5).

The code to illustrate this rule:

#include <iostream>

using namespace std;

class X {
  public:
    X() 
    {
     cout << "constructor" << endl;
    }
    ~X() 
    {
     cout << "destructor" << endl;
    }
};

template <class type>
void ill_formed()
{
  goto lx;
ly:
  type a;
lx:
  goto ly;
}

template <class type>
void ok()
{
ly:
  type a;
lx:
  goto ly;
}

void test_class()
{
  ok<X>();
  // compile error
  ill_formed<X>();
}

void test_scalar() 
{
  ok<int>();
  ill_formed<int>();
}

int main(int argc, const char *argv[]) 
{
  return 0;
}

The code to show the initializer effect:

#include <iostream>

using namespace std;

int test1()
{
  int i = 0;
  // There jumps fo "case 1" and "case 2"
  switch(i) {
    case 1:
      // Compile error because of the initializer
      int r = 1; 
      break;
    case 2:
      break;
  };
}

void test2()
{
  int i = 2;
  switch(i) {
    case 1:
      int r;
      r= 1; 
      break;
    case 2:
      cout << "r: " << r << endl;
      break;
  };
}

int main(int argc, const char *argv[]) 
{
  test1();
  test2();
  return 0;
}
0
votes

It appears that anonymous objects can be declared or created in a switch case statement for the reason that they cannot be referenced and as such cannot fall through to the next case. Consider this example compiles on GCC 4.5.3 and Visual Studio 2008 (might be a compliance issue tho' so experts please weigh in)

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

    switch( i )
    {
    case 42:
        Foo();  // Apparently valid
        break;

    default:
        break;
    }
    return EXIT_SUCCESS;
}