1. 7
  1.  

  2. 6

    As an alternative fix, enum switches are better when they return, i.e. when the switch is in its own function.

    As such, switch should have been an expression, but that’s nothing that can’t be fixed by an enclosing closure:

    auto [f_next, s_suffix] = [&]() -> std::tuple<Foo, std::string_view> {
        switch (f)
        {
            case Foo::Alpha:
                return {Foo::Alpha, "is nothing"};
            case Foo::Beta:
                return {Foo::Gamma, "is important"};
            case Foo::Gamma:
                break; // default
        }
        return {Foo::Alpha, "is very important"};
    }();
    

    There are many reasons that together point at this solution:

    • With the appropriate warning level (-Werror=switch for Gcc), enum switches with unhandled values become compilation errors. But only when you don’t use default. This is great, because compilation errors are better than runtime errors. Not that we will eschew the runtime check, but we must not use the default word.
    • When the switch is in a function of its own, there is a better place for the default handling: After the switch. This place is reachable, as the compiler will insist, which means that you can’t simply forget to handle it (control reaches end of non-void function). What happens here is that the compiler reminds us of all the other physically representable values. Which you may actually want to handle if the enum value was received numerically. If you don’t want to treat them specially, it is zero cost to just designate one of the cases as the default, as above. In any case, we have explicitly handled invalid values too, and the compiler will make sure we do.
    • Making the function anonymous makes it an easy choice for the compiler to inline.
    • Making wrong code look wrong: In order to forget the return, you have to forget the return expression. Only this point is taken care of by the break; case solution in the post.
    1. 6

      This is just like using if (42 == answer) to avoid accidentally typing if (answer = 42). Why use such an awkward style when -Wparentheses completely solves the problem? Similarly, to avoid forgetting break, gcc and clang solve this with -Wimplicit-fallthrough.

      1. 3

        This seems like the same kind of advice as always putting the rvalue on the left in comparisons so that if you accidentally type = instead of == then you get a compile failure. It was a great way of avoiding bugs 20 years ago, but now compilers will warn if you write if (x = y) and require you to write if ((x = y)) to silence the warning if that’s what you actually meant.

        With modern C++, the compiler will require every case to end with either break or [[fallthrough]], and warn if it doesn’t.

        The thing that I find missing from C++ is directed fallthrough. If I were designing the syntax from scratch, break would be the default and fallthrough {label} would be required to explicitly say the label that you fall through. case statements would be allowed to take multiple values.

        1. 3

          This reminds me a lot of the initializer list style that puts the commas on the left. E.g.,

          silly_class::silly_class()
              : fluffy_kittens(1)
              , exuberent_puppies(0)
              , sparkly_unicorns(0)
          {
              // ...
          }
          
          1. 1

            That’s also a good point. That’s what I call an infix comma, and the common problem is what I would describe as infix syntax (separators that go between elements, as opposed to pre- and postfixes): When writing a list vertically, they make you, or permit you to, omit something essential at the beginning or end. Neither is obviously good for adding or subtracting items at either end.

            In the initializer list example, we chose to omit the comma at the top, likely for the aesthetic reason of lining up all the commas below the colon, but the first line is still special, so it would be nicer if the colon was a comma too. That would make the list prefix comma separated.

          2. 3

            -Wfallthrough

            1. 2

              I like the idea. My trouble with adopting it is that switch statement like this are pretty common for me:

              switch (x) {
              case A:
              case B:
              case C: {
                do_something;
                break;
              }
              case D:
              case E:
              case F: {
                do_something_else;
                break;
              }
              }
              

              i.e. each block has a break at the end, but multiple case statements point at the same block.

              And that breaks this proposed style:

              switch (x) {
              break; case A:
              case B:
              case C: {
                do_something;
              }
              break; case D:
              case E:
              case F: {
                do_something_else;
              }
              }
              

              Not really as effective sadly…

              1. 2

                I’ve been calling these break-cases for a while. For some reason the c# compiler doesn’t like it

                1. 2

                  Can’t you just put break at the end of every case, even the last? Seems like an easier solution to me.

                  1. 1

                    We can best-of-both-worlds it by using these on every case except the first.