I have a situation where I would like for two cases in a C++ switch statement to both fall through to a third case. Specifically, the second case would fall through to the third case, and the first case would also fall through to the third case without passing through the second case.
I had a dumb idea, tried it, and it worked! I wrapped the second case in an if (0) {
... }
. It looks like this:
#ifdef __cplusplus
# include <cstdio>
#else
# include <stdio.h>
#endif
int main(void) {
for (int i = 0; i < 3; i++) {
printf("%d: ", i);
switch (i) {
case 0:
putchar('a');
// @fallthrough@
if (0) { // fall past all of case 1 (!)
case 1:
putchar('b');
// @fallthrough@
}
case 2:
putchar('c');
break;
}
putchar('\n');
}
return 0;
}
When I run it, I get the desired output:
0: ac
1: bc
2: c
I tried it in both C and C++ (both with clang), and it did the same thing.
My questions are: Is this valid C/C++? Is it supposed to do what it does?
switch(x) { case A: case B: do_this(); if(x == B) also_do_that(); ... }
. That was also, IMO, horrible. Please, just write stuff like that out as if statements, even if it means you have to repeat one line in two places. Use functions and variables (and documentation!) to reduce the risk of accidentally later updating in only one place.
Yes, this is supposed to work. The case labels for a switch statement in C are almost exactly like goto labels (with some caveats about how they work with nested switch statements). In particular, they do not themselves define blocks for the statements you think of as being "inside the case", and you can use them to jump into the middle of a block just like you could with a goto. When jumping into the middle of a block, the same caveats as with goto apply regarding jumping over initialization of variables, etc.
With that said, in practice it's probably clearer to write this with a goto statement, as in:
switch (i) {
case 0:
putchar('a');
goto case2;
case 1:
putchar('b');
// @fallthrough@
case2:
case 2:
putchar('c');
break;
}
Yes, this is allowed, and it does what you want. For a switch
statement, the C++ standard says:
case and default labels in themselves do not alter the flow of control, which continues unimpeded across such labels. To exit from a switch, see break. [Note 1: Usually, the substatement that is the subject of a switch is compound and case and default labels appear on the top-level statements contained within the (compound) substatement, but this is not required. Declarations can appear in the substatement of a switch statement. — end note]
So when the if
statement is evaluated, control flow proceeds according to the rules of an if
statement, regardless of intervening case labels.
As other answers have mentioned, this is technically allowed by the standard, but it is very confusing and unclear to future readers of the code.
This is why switch ... case
statements should usually be written with function calls and not lots of inline code.
switch(i) {
case 0:
do_zero_case(); do_general_stuff(); break;
case 1:
do_one_case(); do_general_stuff(); break;
case 2:
do_general_stuff(); break;
default:
do_default_not_zero_not_one_not_general_stuff(); break;
}
do_general_stuff
for default, only for case 2 (and 0 and 1). It never runs the switch for an i
outside the 0..2 range, though.
do_general_stuff
for default, only for case 2 (and 0 and 1).
general
and default
were different words. OTOH, it's a known fact that humans can usually still read a word if the middle letters are scrambled; we tend to look at the start and end, so having only the middle be different is probably not ideal for skimmability. Perhaps remove the do_
prefix.
Success story sharing
bee
(in C mode; in C++ they error out on "cannot jump from switch statement to this case label/jump bypasses variable initialization").goto
is the cleaner solution.goto
is much maligned but there's really nothing objectionable about it if you're not making complex control structures out of it manually. The whole "goto considered harmful" was about writing HLL (e.g. C) code that looks like asm emitted by a compiler (jmps back and forth all over the place). There are lots of good structured uses of goto like forward-only (conceptually no different thanbreak
or earlyreturn
), unlikely-retry-loop-only (avoids obscuring common flow path & compensates for lack of nested-continue
), etc.