Make compiler assume that all cases are handled in switch without default

As you can see, there is an absolute certainty that the variable iteration is always 0, 1 or 2.

From the perspective of the toolchain, this is not true. You can call this function from someplace else, even from another translation unit. The only place that your constraint is enforced is in main, and even there it's done in a such a way that might be difficult for the compiler to reason about.

For our purposes, though, let's take as read that you're not going to link any other translation units, and that we want to tell the toolchain about that. Well, fortunately, we can!

If you don't mind being unportable, then there's GCC's __builtin_unreachable built-in to inform it that the default case is not expected to be reached, and should be considered unreachable. My GCC is smart enough to know that this means colorData is never going to be left uninitialised unless all bets are off anyway.

#include <stdint.h>

volatile uint16_t dummyColorRecepient;

void updateColor(const uint8_t iteration)
{
    uint16_t colorData;
    switch(iteration)
    {
    case 0:
        colorData = 123;
        break;
    case 1:
        colorData = 234;
        break;
    case 2:
        colorData = 345;
        break;

    // Comment out this default case to get the warnings back!
    default:
        __builtin_unreachable();
    }
    dummyColorRecepient = colorData;
}

// dummy main function
int main()
{
    uint8_t iteration = 0;
    while (true)
    {
        updateColor(iteration);
        if (++iteration == 3)
            iteration = 0;
    }
}

(live demo)

This won't add an actual default branch, because there's no "code" inside it. In fact, when I plugged this into Godbolt using x86_64 GCC with -O2, the program was smaller with this addition than without it — logically, you've just added a major optimisation hint.

There's actually a proposal to make this a standard attribute in C++ so it could be an even more attractive solution in the future.


Use the "immediately invoked lambda expression" idiom and an assert:

void updateColor(const uint8_t iteration)
{
    const auto colorData = [&]() -> uint16_t
    {
        switch(iteration)
        {
            case 0: return 123;
            case 1: return 234;
        }

        assert(iteration == 2);
        return 345;
    }();

    dummyColorRecepient = colorData;
}
  • The lambda expression allows you to mark colorData as const. const variables must always be initialized.

  • The combination of assert + return statements allows you to avoid warnings and handle all possible cases.

  • assert doesn't get compiled in release mode, preventing overhead.


You can also factor out the function:

uint16_t getColorData(const uint8_t iteration)
{
    switch(iteration)
    {
        case 0: return 123;
        case 1: return 234;
    }

    assert(iteration == 2);
    return 345;
}

void updateColor(const uint8_t iteration)
{
    const uint16_t colorData = getColorData(iteration);
    dummyColorRecepient = colorData;
}

You can get this to compile without warnings simply by adding a default label to one of the cases:

switch(iteration)
{
case 0:
    colorData = 123;
    break;
case 1:
    colorData = 234;
    break;
case 2: default:
    colorData = 345;
    break;
}

Alternatively:

uint16_t colorData = 345;
switch(iteration)
{
case 0:
    colorData = 123;
    break;
case 1:
    colorData = 234;
    break;
}

Try both, and use the shorter of the two.