If I declare/define an object in a if single statement then that object is visible only within the if statement and it is defined and allocated only if the if condition succeeds.
if(true)
int x = 10;
std::cout << x << '\n'; // error: x is not in scope
This is really so logical but why this doesn't apply to case statement too?
char c = 'b';
switch(c){
case 'a':
int value; // ok. not-initialized
break;
case 'b':
value = 100; // why still visible and when initialized? (this is an assignment)
std::cout << value << '\n'; // 100
break;
}
As you can see
chas the valuebso the first case labelcase 'a'is not executed so whenvalueis defined as long as an object is defined when control passes through its definition?Why
valueis visible under case labelcase 'b'as long as it is defined under a case label that is not executed?What is the point in allowing that? (defining objects even the case label under which they are defined doesn't match)?
CodePudding user response:
The block of the switch statement starts at opening curly brace and closes at closing one. The break instructions do not mark any end of block.
So both cases belongs to the same instruction block and share the same local variables.
To limit the variable visibility to a block you need to add more curly braces:
switch(c){
case 'a':{
int value; // ok. not-initialized
}
break;
case 'b':{
value = 100; // You should get an error now
std::cout << value << '\n'; // 100
}
break;
}
So the remaining question is why break is not an end of block?
I would say that because break may be conditional.
switch(c){
case 'a':
int value; // ok. not-initialized
if (condition)
break;
case 'b':
value = 100; // It is normal in this case to be able to refer to previously defined variable
std::cout << value << '\n'; // 100
break;
}
Though I don't see a real use case for such code!
CodePudding user response:
A switch statement and an if statement are relatively similar on how they are compiled, that is they both translate into a conditional jump in assembly. The main difference is that if branches are always mutually exclusive - if you enter the if, you are never entering the else - but switch branches are not. So it makes sense for the compiler to behave accordingly.
We usually insert a break at the end of each case because otherwise the instructions from the following cases would be executed too. For example:
int x = 1;
switch(x) {
case 1:
std::cout << "x was 1";
// No break!
case 2:
std::cout << "... But was it also 2?" << std::endl;
break;
default:
break;
}
Spookily outputs:
x was 1... But was it also 2?
Basically switch is a jump instruction, case specifies where to land but says nothing about jumping out again. That job is done by break.
Using the dreaded goto statements and labels, a very similar code would be this:
int x = 1;
if (x == 1)
goto x_was_1;
else if (x == 2)
goto x_was_2;
else
goto out;
x_was_1:
std::cout << "x was 1";
x_was_2:
std::cout << "... But was it also 2?" << std::endl;
goto out;
out:
// Rest of the code...
Which outputs the same thing. You can also see how adding a break is equivalent to adding goto out in this case.
In conclusion, the content of a switch statement is a single code block and all the variables declared inside it are visible throughout because you may traverse the whole block. Just like you expect variables to be seen below a break in a while loop, you can expect them to be visible in this case too. Though I believe using this is extremely bad practice, very unreadable and likely useless.
