103

What is the point with using { and } in a case statement? Normally, no matter how many lines are there in a case statement, all of the lines are executed. Is this just a rule regarding older/newer compilers or there is something behind that?

int a = 0;
switch (a) {
  case 0:{
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
  }
}

and

int a = 0;
switch (a) {
  case 0:
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
}
mahmood
  • 23,197
  • 49
  • 147
  • 242
  • 58
    One use can be to limit the scope of variables declared in the case statement. – Abhishek Bansal Nov 17 '13 at 13:25
  • 3
    http://stackoverflow.com/questions/92396/why-cant-variables-be-declared-in-a-switch-statement – SomeWittyUsername Nov 17 '13 at 13:25
  • 1
    Too much indentation, too. The cases are just labels within the switch statement's block: they don't introduce additional nesting, so they should align with the `switch` keyword, and in the second example, the enclosed statements only indented once. Note how you have an awkward four space de-indent after the `break;`. – Kaz Nov 17 '13 at 20:08
  • Note, the accepted answer is only partially correct as [Jack's comment](http://stackoverflow.com/questions/20031147/using-in-a-case-statement-why#comment29856700_20031179) points out and misses some subtleties, which I address in my answer. – Shafik Yaghmour Nov 19 '13 at 14:57
  • Just as an FYI: in C (even C11) rather than C++, you cannot label a declaration; they are not in the syntactic category `statement`. In C++, you can (one component of the syntactic category `statement` is `declaration statement`). – Jonathan Leffler Nov 20 '13 at 03:44

6 Answers6

200

The {} denotes a new block of scope.

Consider the following very contrived example:

switch (a)
{
    case 42:
        int x = GetSomeValue();
        return a * x;
    case 1337:
        int x = GetSomeOtherValue(); //ERROR
        return a * x;
}

You will get a compiler error because x is already defined in the scope.

Separating these to their own sub-scope will eliminate the need to declare x outside the switch statement.

switch (a)
{
    case 42: {
        int x = GetSomeValue();
        return a * x; 
    }
    case 1337: {
        int x = GetSomeOtherValue(); //OK
        return a * x; 
    }
}

Warning:

Declare and initialize a variable inside case without {} surrounded is wrong:

#include <iostream>
using namespace std;
int main() {
    int b = 3;
    switch (b) {
    case 3:
        int a = 3; //compilation error: "initialization of 'a' skipped by 'case' label"
        return a * b;
    case 1:
        return a * b;
    }
}
Rick
  • 7,007
  • 2
  • 49
  • 79
Rotem
  • 21,452
  • 6
  • 62
  • 109
  • 12
    Actually IMO you will get a compiler error even if you skip the second declaration of variable x. – Abhishek Bansal Nov 17 '13 at 13:30
  • @AbhishekBansal: You should not be getting an error, though if `case 1337:` still `return a * x;` then you should be getting a warning that `x` may be uninitialized. – Matthieu M. Nov 17 '13 at 13:31
  • 1
    Although over using this style and putting big blocks inside the switch statement will make it unreadable to follow the cases. I prefer to keep the statements tiny. – masoud Nov 17 '13 at 13:38
  • 2
    @MatthieuM. I know for a fact that MS Visual Studio 2010 will have the behavior Abhishek indicates: it will not compile any variable declaration inside a case (unless you use curly brackets to denote a new scope within that case). Whether that matches the standards, I don’t know. – KRyan Nov 17 '13 at 14:44
  • 1
    @KRyan: it does not, but it's such a much safer alternative that I can hardly blame them for enforcing this. – Matthieu M. Nov 17 '13 at 14:47
  • Using or omitting `{ }` is a matter of personal style, yet one still has to watch out for putting either `break` or `return`. I wonder if K&R (C lang creators) were happy with their decision of default fallthrough behavior with explicit breaks, instead of choosing deafult non-fallthrough with explicit activation of fallthrough. – Maciej Jastrzebski Nov 17 '13 at 20:47
  • 7
    Section 6.7(3) of the standard (numbering for the 2005 draft) specifies that you cannot jump an initialisation, so you cannot have initialisation in a case block. – Jack Aidley Nov 18 '13 at 11:50
  • 1
    @JackAidley is correct here, although you basically have the concept, your example and explanation are not correct. It is never okay to jump over a declaration with an initialization, so the code would be an error regardless of the second declaration of `x`. See my answer for the details. – Shafik Yaghmour Nov 19 '13 at 15:06
  • 1
    @MatthieuM. jumping a declaration with an initialization is a violation of the standard and both `gcc` and `clang` do not allow it and I would be surprised if visual studio did. – Shafik Yaghmour Nov 19 '13 at 15:10
  • @Shafik I never claimed that in the absence of the declaration the code would be fine, just that in my example, the declaration is where the error would be. – Rotem Nov 19 '13 at 15:18
  • 1
    That is a confusing example, at least two other users felt the need to comment on that so it is clearly not obvious and it misses the whole concept that jumping a declaration with an initialization is not valid code, which would probably confuse a novice user trying to learn something from this code example. At least you should remove the initialization from the declaration. – Shafik Yaghmour Nov 19 '13 at 15:27
  • @ShafikYaghmour I'm usually very accepting of suggestions and criticism but I am having trouble understanding how the example is unclear. It is only there to show invalid code in which adding `{` and `}` to each case will produce valid code. I've omitted the version with `{` and `}` because it's repetitive and redundant. – Rotem Nov 19 '13 at 18:36
  • @ShafikYaghmour: It depends whether the diagnosis is mandatory; some languages that are ill-formed (for example wrt. the One Definition Rules) require no diagnosis, just because sometime it is expensive/difficult to perfectly detect the cases reliably. And I know not whether it is. – Matthieu M. Nov 19 '13 at 19:29
  • 1
    A less contrived example might show reliance on scoping for object lifetimes rather than name scope, i.e. if some cases, but not all, need a scoped mutex lock. – boycy Nov 20 '13 at 12:22
  • @ShafikYaghmour I agree with you ;). I think the concept of jumping a declaration with an initialization is not valid code should be mentioned in this answer. And the first code snippet could be misleading indeed. – Rick Jan 16 '21 at 03:25
23

TL;DR

The only way you can declare a variable with an intializer or some non-trivial object inside a case is to introduce a block scope using {} or other control structure that has it's own scope like a loop or if statement.

Gory details

We can see that cases are just labeled statements like the labels used with a goto statement(this is covered in the C++ draft standard section 6.1 Labeled statement) and we can see from section 6.7 paragraph 3 that jumping pass a declaration is not allowed in many cases, including those with an initialization:

It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps87 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).

and provides this example:

void f() {
 // ...
 goto lx; // ill-formed: jump into scope of a

 ly:
  X a = 1;
 // ...
 lx:
  goto ly; // OK, jump implies destructor
          // call for a followed by construction
          // again immediately following label ly
}

Note, there are some subtleties here, you are allowed to jump past a scalar declaration that does not have an initialization, for example:

switch( n ) 
{
    int x ;
    //int x  = 10 ; 
    case 0:
      x = 0 ;
      break;
    case 1:
      x = 1 ;
      break;
    default:
      x = 100 ;
      break ;
}

is perfectly valid(live example). Of course if you want to declare the same variable in each case then they will each need their own scope but it works the same way outside of switch statements as well, so that should not be a big surprise.

As for the rationale for not allowing jump past initialization, defect report 467 although covering a slightly different issue provides a reasonable case for automatic variables:

[...]automatic variables, if not explicitly initialized, can have indeterminate (“garbage”) values, including trap representations, [...]

It is probably more interesting to look at the case where you extend a scope within a switch over multiple cases the most famous examples of this is probably Duff's device which would look something like this:

void send( int *to, const int *from, int  count)
{
        int n = (count + 7) / 8;
        switch(count % 8) 
        {
            case 0: do {    *to = *from++;   // <- Scope start
            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);    // <- Scope end
        }
}
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
6

It is a habit that allows you to inject variable declarations with the resulting destructor (or scope conflicts) into case clauses. Another way of looking at it is they are writing for the language they wish they had, where all flow control consists of blocks and not sequences of statements.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
4

Check this a basic compiler restriction and you will start wondering whats happening :

int c;
c=1;

switch(c)
{
    case 1:
    //{
        int *i = new int;
        *i =1;
        cout<<*i;
        break;
    //}

    default : cout<<"def";
}

This will give you an error:

error: jump to case label [-fpermissive]
error:   crosses initialization of ‘int* i’

While this one will not:

int c;
c=1;

switch(c)
{
    case 1:
    {
        int *i = new int;
        *i =1;
        cout<<*i;
        break;
    }

    default : cout<<"def";
}
Uyghur Lives Matter
  • 18,820
  • 42
  • 108
  • 144
Parag
  • 662
  • 9
  • 15
1

Using brackets in switch denotes a new block of scope as said by Rotem.

But it can be as well for clarity when you read. To know where the case stops as you might have conditionnal break in it.

dyesdyes
  • 1,147
  • 3
  • 24
  • 39
1

The reasons might be:

  1. Readability, it visually enhances each case as a scoped section.
  2. Declaring the a different variables with the same name for several switch cases.
  3. Micro optimizations- scope for a really expensive resource allocated variable that you want to destruct as soon as you leave the scope of the case, or even a more spaghetti scenario of the "GOTO" command usage.
Roman Ambinder
  • 369
  • 3
  • 7