131

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?

cigien
  • 57,834
  • 11
  • 73
  • 112
Mark Adler
  • 101,978
  • 13
  • 118
  • 158
  • 1
    I'd suggest putting the curly braces around the content of case 1 actually. It just looks a bit cleaner that way and allows you to use 'block local variables'. The case statement makes no difference to the program flow. – Refugnic Eternium Oct 22 '20 at 05:51
  • 35
    Yes, this is valid and works for pretty much the same reasons why [Duff's device](https://stackoverflow.com/questions/514118/how-does-duffs-device-work) does. – dxiv Oct 22 '20 at 06:00
  • 2
    There's a [`fallthrough`](https://en.cppreference.com/w/cpp/language/attributes/fallthrough) attribute, to help the code be self-documenting without using a comment. And squelch (otherwise helpful) compiler warnings. – Eljay Oct 22 '20 at 10:50
  • 44
    Note that code like this will get you tossed out of any code walkthrough in an environment that cares even just a little about readability and maintainability. – Andrew Henle Oct 22 '20 at 13:09
  • 17
    This is horrible. Even more horrible than Duff's device, mind you. Related, I also recently saw something like `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. – ilkkachu Oct 22 '20 at 16:47
  • 2
    @eljay -- the C compiler I'm using (old Turbo C) DOESN'T have a fallthrough, but I successully extended the language so it has one. Just use fallthrough where you would normally put a break. To implement this, use the following at the beginning of the program or in a header: "#define fallthrough". Works like a charm. – Jennifer Oct 22 '20 at 18:53
  • 50
    :-) For those who were injured or maimed due to looking at that code, I didn't say it was a _good_ idea. In fact, I said it was a dumb idea. – Mark Adler Oct 22 '20 at 19:28
  • 2
    The [language-lawyer] tag was removed, but I'm adding it back. OP clearly states, "here is a piece of code, it does what I want, but does the language allow it?" – cigien Oct 23 '20 at 00:37
  • Thigs get messy when you declare variables inside the `if(0)`'s block – M.M Oct 23 '20 at 01:55
  • 1
    `goto case 2;`. Oh wait, this is not that language. – CompuChip Oct 23 '20 at 07:57
  • 2
    At least this is on case (no pun intended) where `if (0) {` differs fro `#if 0` – Hagen von Eitzen Oct 23 '20 at 15:33
  • 5
    Note that constructions inside switches like this do NOT play well with RAII :( – Mooing Duck Oct 23 '20 at 21:41
  • 1
    Some of the greatest things in history started as "dumb idea"... if we won't try new things, how will we ever learn? :-D – Shadow The GPT Wizard Oct 24 '20 at 06:49
  • 1
    I worked as a C++ programmer for years and never realised switch statements worked this way. The `switch` `case` and `break` here act as little more than glorified `goto` statements. – Paul D Oct 28 '20 at 09:05

3 Answers3

66

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;
    }
R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 1
    _"When jumping into the middle of a block, the same caveats as with goto apply regarding jumping over initialization of variables, etc"_ What caveats would those be? You _cannot_ jump over the initialization of variables. – Asteroids With Wings Oct 22 '20 at 15:45
  • 4
    @AsteroidsWithWings, do you mean "cannot" from a standards-conforming perspective, or from the kind of practical perspective that a compiler will not allow it? Because my GCC does allow it in C mode, with a warning. It doesn't allow it in C++ mode though. – ilkkachu Oct 22 '20 at 16:47
  • @ilkkachu will your compiler in c++ mode complain when presented this little-modified OP's code? I really HOPE it does not compile: `switch (i) { case 0: putchar('a'); if (0) { char bee = 'b'; case 1: putchar(bee); } case 2: putchar('c'); }` – quetzalcoatl Oct 22 '20 at 18:45
  • 5
    @quetzalcoatl gcc-9 and clang-6 both allow this code, warning about potentially uninitialized `bee` (in C mode; in C++ they error out on "cannot jump from switch statement to this case label/jump bypasses variable initialization"). – Ruslan Oct 22 '20 at 18:56
  • @quetzalcoatl: If you want to throw code at compilers and see their warnings, https://godbolt.org/ Compiler Explorer has various versions of gcc, clang, ICC, and MSVC, for C or C++. (Or you can use `-xc` / `-xc++` on the gcc or clang command line to select language without the inconvenience of switching languages in the compiler-explorer dropdown and losing all your other settings and source.) – Peter Cordes Oct 23 '20 at 00:48
  • 10
    You know you’re doing something wrong when using `goto` is the cleaner solution. – Konrad Rudolph Oct 23 '20 at 16:24
  • 11
    @KonradRudolph: `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 than `break` or early `return`), unlikely-retry-loop-only (avoids obscuring common flow path & compensates for lack of nested-`continue`), etc. – R.. GitHub STOP HELPING ICE Oct 23 '20 at 18:50
  • I believe you can just `goto case 2` and it will work - you don't need the extra label. – TheHans255 Oct 24 '20 at 01:59
  • @TheHansinator: At least in C, `case 2` is not a label you can use with `goto`. Not sure about C++. – R.. GitHub STOP HELPING ICE Oct 24 '20 at 03:15
  • You're right, neither C nor C++ let you jump to switch cases. C# is the one that does, and in that language a feature like that is more necessary since it does not allow switch statement fall through. – TheHans255 Oct 24 '20 at 14:31
60

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.

cigien
  • 57,834
  • 11
  • 73
  • 112
  • 14
    As a specific case of this, one can look at [Boost.Coroutines](https://www.boost.org/doc/libs/1_54_0/boost/asio/coroutine.hpp) for a set of stomach-churning macros which take advantage of this rule (and a half dozen other corner cases of C++) to implement coroutines using C++03 rules. They were not made part of the language until C++20, but these macros made them work... as long as you took your antacids first! – Cort Ammon Oct 22 '20 at 20:49
29

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;
}
Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • Note that the code in the question doesn't `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. – Peter Cordes Oct 23 '20 at 00:52
  • @PeterCordes -- note that the code in the answer doesn't `do_general_stuff` for default, only for case 2 (and 0 and 1). – Pete Becker Oct 23 '20 at 12:53
  • A suggestion - maybe the default case should be removed or renamed? It's quite easy to miss the different function names. – see Oct 23 '20 at 14:29
  • @PeteBecker: Oh, IDK how I missed that `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. – Peter Cordes Oct 23 '20 at 22:49
  • @PeterCordes: Whether or not that works depends upon whether the text in question is contrived to facilitate such "reading". If tye cagdm hyod taxe in glorebl, ybu syabyd be arge to rbod trbs. – supercat Oct 24 '20 at 19:31
  • 1
    @supercat: That "well-known fact" is not about replacing the middle letters by different ones, but just about mixing up the order of then. "If the cialm wree true in graneel, you sluhod be albe to raed tihs." – Michael Karcher Oct 24 '20 at 19:41
  • Even that claim is still prone to fail with in texts which lack sufficient short words to assist the decoding of longer ones. – supercat Oct 24 '20 at 19:46