C++ Force compile-time error/warning on implicit fall-through in switch

C++Switch StatementCompiler Warnings

C++ Problem Overview


switch statements can be super useful, but lead to a common bug where a programmer forgot a break statement:

switch(val) {
    case 0:
        foo();
        break;
    case 1:
        bar();
        // oops
    case 2:
        baz();
        break;
    default:
        roomba();
}

You won't get a warning obviously since sometimes fall-through is explicitly desired. Good coding style suggests to comment when your fall-through is deliberate, but sometimes that is insufficient.

I'm pretty sure the answer to this question is no, but: is there any way currently (or proposed in the future) to be able to ask the compiler to throw an error (or at least a warning!) if your case does not have at least one of break; or something to the effect of // fallthru? It would be nice to have a defensive programming option for using switch statements.

C++ Solutions


Solution 1 - C++

Well clang has -Wimplicit-fallthrough which I did not know about but found by using -Weverything. So for this code it gives me the following warning (see it live):

warning: unannotated fall-through between switch labels [-Wimplicit-fallthrough]
case 2:
^
note: insert '[[clang::fallthrough]];' to silence this warning
case 2:
^
[[clang::fallthrough]]; 
note: insert 'break;' to avoid fall-through
case 2:
^
break; 

The only documentation I can find for this flag is in the Attribute Reference which says:

> The clang::fallthrough attribute is used along with the > -Wimplicit-fallthrough argument to annotate intentional fall-through between switch labels. It can only be applied to a null statement > placed at a point of execution between any statement and the next > switch label. It is common to mark these places with a specific > comment, but this attribute is meant to replace comments with a more > strict annotation, which can be checked by the compiler.

and provides an example of how to mark explicit fall-through:

case 44:  // warning: unannotated fall-through
g();
[[clang::fallthrough]];
case 55:  // no warning

This use of an attribute to mark explicit fall-through has the disadvantage of not being portable. Visual Studio generate an error and gcc generates the following warning:

warning: attributes at the beginning of statement are ignored [-Wattributes]

which is a problem if you want to use -Werror.

I tried this with gcc 4.9 and it looks like gcc does not support this warning:

>error: unrecognized command line option '-Wimplicit-fallthrough'

As of GCC 7, -Wimplicit-fallthrough is supported and __attribute__((fallthrough)) can be used to suppress the warnings when fallthrough is intentional. GCC does recognize "fallthrough" comments in certain scenarios, but it can be confused fairly easily.

I do not see a way of generating such a warning for Visual Studio.

Note, Chandler Carruth explains that -Weverything is not for production use:

> This is an insane group that literally enables every warning in Clang. > Don't use this on your code. It is intended strictly for Clang > developers or for exploring what warnings exist.

but it is useful for figuring out what warnings exist.

###C++17 changes###

In C++17 we get the attribute [[fallthrough]] covered in [dcl.attr.fallthrough]p1:

> The attribute-token fallthrough may be applied to a null statement (9.2); such a statement is a fallthrough statement. The attribute-token fallthrough shall appear at most once in each attribute-list and no attributeargument- clause shall be present. A fallthrough statement may only appear within an enclosing switch statement (9.4.2). The next statement that would be executed after a fallthrough statement shall be a labeled statement whose label is a case label or default label for the same switch statement. The program is >ill-formed if there is no such statement. > >... > > > > [ Example: void f(int n) { void g(), h(), i(); switch (n) { case 1: case 2: g(); [[fallthrough]]; case 3: // warning on fallthrough discouraged h(); case 4: // implementation may warn on fallthrough i(); [[fallthrough]]; // ill-formed } } —end example ]

See live example using attribute.

Solution 2 - C++

I always write a break; before each case, as follows:

switch(val) {
    break; case 0:
        foo();
    break; case 1:
        bar();
    break; case 2:
        baz();
    break; default:
        roomba();
}

This way, it is much more obvious to the eye if a break; is missing. The initial break; is redundant I suppose, but it helps to be consistent.

This is a conventional switch statement, I've simply used whitespace in a different way, removing the newline that is normally after a break; and before the next case.

Solution 3 - C++

Advice: if you consistently put a blank line in between case clauses, the absence of a 'break' becomes more visible to a human skimming the code:

switch (val) {
    case 0:
        foo();
        break;

    case 1:
        bar();

    case 2:
        baz();
        break;

    default:
        roomba();
}

This isn't as effective when there's a lot of code inside individual case clauses, but that tends to be a bad code smell in itself.

Solution 4 - C++

Here's an answer to compulsively hate.

First, switch statements are fancy gotos. They can be combined with other control flow (famously, Duff's Device), but the obvious analogy here is a goto or two. Here's a useless example:

switch (var) {
    CASE1: case 1:
        if (foo) goto END; //same as break
        goto CASE2; //same as fallthrough
    CASE2: case 2:
        break;
    CASE3: case 3:
        goto CASE2; //fall *up*
    CASE4: case 4:
        return; //no break, but also no fallthrough!
    DEFAULT: default:
        continue; //similar, if you're in a loop
}
END:

Do I recommend this? No. In fact, if you're considering this just to annotate a fall-through, then your problem is actually something else.

This sort of code does make it very clear that a fall-through can happen in case 1, but as the other bits show, this is a very powerful technique in general that also lends itself to abuse. Be careful if you use it.

Forgetting a break? Well, then you'll also occasionally forget whatever annotation you pick. Forgetting to account for fall-through when changing a switch statement? You're a bad programmer. When modifying switch statements(or really any code), you need to first understand them.


Honestly, I very seldom make this kind of error (forgetting a break)--certainly less than I made other "common" programming errors (strict aliasing, for example). To be safe, I currently do (and recommend you to do) just write //fallthrough, since this at least clarifies intention.

Other than that, it's a just a reality that programmers need to accept. Proofread your code after you write it and find the occasional problem with debugging. That's life.

Solution 5 - C++

You can use a "python style" switch as follows (see it live, perhaps to one's surprise this is actually legal construct accoriding to the standard).

The key trick is to make the entire switch statement a simple statement - i.e. there is scope block, as usual.

Instead, we start a chaining if-statement interspersed with case labels. The if statement itself is unreachable, but you can chain with else if(/*irrelevant*/) to continue the switch in a way that never falls through.

The else nicely excludes the other branches. In effect it's a "switch tree" now, with labels that bypass the conditions.

switch (a) if (false)
    case 4:
    case 5:
    case 6:
        std::cout << "4,5,6" << std::endl; //single statement
        else if (false)
    case 7:
    case 8:
        { //compound is ok too
            std::cout << "7,";
            std::cout << "8" << std::endl;
        }
        else if (false)
    default:
        std::cout << "default" << std::endl;

Note that:

  • the actual condition expression is unused (the case labels jump past the condition)

  • you could actually use a macro like

      #define FORCEBREAK else if(false)
    

    to highlight the meaning.

Cons:

  • That last aspect kind of highlights a flaw: it's strictly more work, and C++17 [[fallthrough]] is mostly just better
  • You now need to silence the unreachable code warning that compilers give (e.g. GCC requires -Wno-switch-unreachable to accept it without warning)

Pros:

  • It's fancy
  • It teaches about the flexibilities of switch statements
  • It does force people to correctly break their switch cases, or it will fail with compile-time errors
  • It applies to C as well with minor modifications

Live On Compiler Explorer

#include <iostream>

void switchless(int a) {
    switch (a) if (false)
        case 4:
        case 5:
        case 6:
            std::cout << "4,5,6" << std::endl; //single statement
            else if (false)
        case 7:
        case 8:
            { //compound is ok too
                std::cout << "7,";
                std::cout << "8" << std::endl;
            }
            else if (false)
        default:
            std::cout << "default" << std::endl;
}

int main() {
    for (int i = 0; i < 10; ++i)
        switchless(i);
}

Prints

default
default
default
default
4,5,6
4,5,6
4,5,6
7,8
7,8
default

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBarryView Question on Stackoverflow
Solution 1 - C++Shafik YaghmourView Answer on Stackoverflow
Solution 2 - C++Aaron McDaidView Answer on Stackoverflow
Solution 3 - C++zwolView Answer on Stackoverflow
Solution 4 - C++imallettView Answer on Stackoverflow
Solution 5 - C++AnArrayOfFunctionsView Answer on Stackoverflow