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
casemust be a compile-time constant, meaning a literal or aconst int. You cannot switch on astd::string, and you cannot write a case for a range of values. breakis 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.defaultruns when nothing matched, in the role of a finalelse.
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.