Course outline · 0% complete

0/27 lessons0%

Course overview →

switch: Choosing Among Fixed Cases

lesson 3-2 · ~9 min · 7/27

One value against many constants

switch exists because comparing one value against many fixed constants is common enough to deserve dedicated syntax. Menu dispatch, state machines, day-of-week tables, and instruction decoding all have that shape.

The payoff is real rather than cosmetic. The compiler can turn a switch into a single jump-table lookup, meaning one indexed jump, instead of testing each else if in turn.

switch

When you are comparing one integer or char against a list of fixed values, switch reads better than a ladder of else-ifs:

int day = 3;
switch (day) {
    case 1: std::cout << "Mon\n"; break;
    case 2: std::cout << "Tue\n"; break;
    case 3: std::cout << "Wed\n"; break;
    default: std::cout << "unknown\n"; break;
}

Rules that matter:

  • Each case must be a compile-time constant, meaning a literal or a const int. You cannot switch on a std::string, and you cannot write a case for a range of values.
  • break is mandatory at the end of each case. Without it, execution falls through into the next case's code, which is occasionally useful and usually a bug.
  • default runs when nothing matched, in the role of a final else.

Python only recently gained match. C++'s switch is older and simpler, being a fast jump on a number and nothing more.

A missing break, and the fix

The switch below has no break after case 2, and the consequence is visible in the output. After printing Tue it continues straight into the body of case 3 and prints Wed as well.

#include <iostream>

int main() {
    int day = 2;
    switch (day) {
        case 1: std::cout << "Mon\n"; break;
        case 2: std::cout << "Tue\n";        // missing break!
        case 3: std::cout << "Wed\n"; break;
        default: std::cout << "unknown\n"; break;
    }
    return 0;
}

Adding the missing break; after the case 2 statement gives the intended single line:

#include <iostream>

int main() {
    int day = 2;
    switch (day) {
        case 1: std::cout << "Mon\n"; break;
        case 2: std::cout << "Tue\n"; break;
        case 3: std::cout << "Wed\n"; break;
        default: std::cout << "unknown\n"; break;
    }
    return 0;
}

Output

Tue

The important detail is that case 3: is never tested. Once a switch has jumped to a matching label, the later labels are just markers in the code that execution runs past, so day being 2 has no bearing on whether the Wed line executes.

Fall-through as a feature

Because execution continues until it hits a break, several case labels can deliberately share one body. This is the single situation where omitting break between cases is idiomatic rather than a bug:

char c = 'e';
switch (c) {
    case 'a': case 'e': case 'i': case 'o': case 'u':
        std::cout << "vowel\n";
        break;
    default:
        std::cout << "consonant\n";
        break;
}

All five vowel labels funnel into the same two lines, and anything else lands on default. Notice that the labels stacked this way have no statements of their own at all, which is what distinguishes intentional stacking from a forgotten break after real code.

The vowel classifier in a full program

The stacked-label switch as a complete program, with c holding a vowel.

#include <iostream>

int main() {
    char c = 'e';
    switch (c) {
        case 'a': case 'e': case 'i': case 'o': case 'u':
            std::cout << "vowel\n";
            break;
        default:
            std::cout << "consonant\n";
            break;
    }
    return 0;
}

Output

vowel

With c holding 'k' instead, none of the five vowel labels match and default catches it, printing consonant.

The default label is doing real work here, and its name is slightly misleading. It catches everything unmatched, which includes genuine consonants but also digits, spaces, and punctuation, so a switch like this one is only honest about its output if the input is known to be a letter. A stricter version would add labels for the uppercase vowels too, since 'E' is a different char from 'e' and would currently be reported as a consonant.

What a missing break does

When a matching case has no break at the end, execution continues straight into the next case's statements, and keeps going until it reaches a break or the closing brace of the switch. This is called fall-through.

The reason is that a switch jumps to the matching label and then executes downward, treating later case labels as nothing more than markers. Their values are never compared, because the comparison already happened at the jump.

It compiles without complaint, which makes a forgotten break one of the classic silent C++ bugs. Modern code marks the intentional cases with the [[fallthrough]] attribute, which documents the choice for readers and silences compiler warnings about it:

switch (level) {
    case 2:
        std::cout << "extra logging\n";
        [[fallthrough]];
    case 1:
        std::cout << "basic logging\n";
        break;
}

Here level 2 deliberately gets both lines, and the attribute tells everyone that was the plan.