Lesson 2 of 6
When you're comparing one value against a list of constants, switch is clearer than a long
if/else if chain:
#include <iostream>
int main() {
int day = 3;
switch (day) {
case 1:
std::cout << "Monday\n";
break;
case 2:
std::cout << "Tuesday\n";
break;
case 3:
std::cout << "Wednesday\n";
break;
default:
std::cout << "Some other day\n";
break;
}
return 0;
}
Wednesday
Execution jumps straight to the matching case, runs until it hits break, then leaves the
switch. default catches everything unmatched.
The rules and the limits
- The switch value must be an integer type or an enum —
int,char,bool,std::size_t. Notstd::string, and notdouble. - Each
caselabel must be a compile-time constant (lesson 3.7). You can't switch on a range or a variable. - Case values must be unique.
If you need string matching or ranges, use if/else if.
Fallthrough: the missing break
This is the feature everyone gets bitten by once. Without break, execution falls through
into the next case:
#include <iostream>
int main() {
int level = 1;
switch (level) {
case 1:
std::cout << "one\n";
case 2:
std::cout << "two\n";
case 3:
std::cout << "three\n";
break;
default:
std::cout << "other\n";
}
return 0;
}
one
two
three
level is 1, so it matches case 1 — and then keeps going through cases 2 and 3 until it
finally hits a break. The case labels are entry points, not self-contained blocks.
Nine times out of ten this is a bug: a forgotten break. Because it produces plausible-looking
output rather than a crash, it can survive testing.
Deliberate fallthrough
The tenth time it's exactly what you want — grouping several cases that share behaviour:
#include <iostream>
int main() {
char grade = 'B';
switch (grade) {
case 'A':
case 'B':
case 'C':
std::cout << "Pass\n";
break;
case 'D':
case 'F':
std::cout << "Fail\n";
break;
default:
std::cout << "Unknown grade\n";
break;
}
return 0;
}
Pass
Stacked labels with no code between them are idiomatic and read fine — nobody misreads that as a
missing break.
The ambiguous case is falling through after running code. C++17 added an attribute to say you meant it:
#include <iostream>
int main() {
int verbosity = 2;
switch (verbosity) {
case 2:
std::cout << "detailed info\n";
[[fallthrough]];
case 1:
std::cout << "basic info\n";
break;
default:
break;
}
return 0;
}
detailed info
basic info
[[fallthrough]]; documents the intent to the next reader and silences the compiler warning
(-Wimplicit-fallthrough, included in -Wextra). Without it, a reviewer can't tell your
deliberate fallthrough from a bug.
Declaring variables inside a case
A subtle rule: you can't declare a variable directly inside a case without braces, because
later cases would jump over its initialization:
// WILL NOT COMPILE
int main() {
int option = 1;
switch (option) {
case 1:
int value = 42; // jumped over by case 2
return value;
case 2:
return 0;
}
return 0;
}
error: jump to case label ... crosses initialization of 'int value'
Add braces to give it a scope of its own:
#include <iostream>
int main() {
int option = 1;
switch (option) {
case 1: {
int value = 42;
std::cout << value << "\n";
break;
}
case 2:
std::cout << "two\n";
break;
}
return 0;
}
42
Always write default
Even when you think you've covered everything:
#include <iostream>
int main() {
int status = 99;
switch (status) {
case 0:
std::cout << "OK\n";
break;
case 1:
std::cout << "Warning\n";
break;
default:
std::cout << "Unexpected status: " << status << "\n";
break;
}
return 0;
}
Unexpected status: 99
Without default, an unexpected value does nothing at all — silently. A default that reports
the surprise turns a mystery into a message.
Try it
Write a calculator that takes a char operator ('+', '-', '*', '/') and two double
values, and prints the result using a switch. Handle division by zero, and handle an unknown
operator.
Checkpoint
switchcompares one integer or enum value against compile-time constantcaselabels.- It can't switch on
std::string,double, or ranges — useif/else iffor those. - Without
break, execution falls through into the next case; that's usually a bug. - Stacked empty labels are idiomatic; use
[[fallthrough]];when falling through after code. - Wrap a case in braces if you declare a variable inside it.
- Always write a
default, even if only to report the unexpected.
Questions about this lesson
Log in to ask a question.