The C++ language provides the switch statement which can be used to replace the set of if statements (see If Statements in Modern C++). First of all, let’s define the enum type Traffic_light_color as follows:
|
1 |
enum class Traffic_light_color { red, yellow, green }; |
Then, the following snippet:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
// Snippet 1 #include <stdexcept> #include <string_view> std::string_view to_string(const Traffic_light_color value) { if (value == Traffic_light_color::red) return "red"; else if (value == Traffic_light_color::yellow) return "yellow"; else if (value == Traffic_light_color::green) return "green"; throw std::logic_error{"bug detected"); } |
could be replaced with:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Snippet 2 #include <stdexcept> #include <string_view> std::string_view to_string(const Traffic_light_color value) { switch (value) { case Traffic_light_color::red: return "red"; case Traffic_light_color::yellow: return "yellow"; case Traffic_light_color::green: return "green"; } throw std::logic_error{"bug detected"}; } |
The second snippet is easier to read since the intention to compare a value with a set of constants is expressed explicitly. Furthermore, the compilers like the latest generation of Embarcadero RAD Studio C++ can optimize the second snippet above, and instead of brute checking values again and again the compiler can use some implementation of a jump table.
Please note, that each case of the second snippet above ends with return statement since it’s natural way to terminate the case-branch in this example. However, break or goto statements could be also used in order to terminate the case-branches of switch statements. The lack of one or the other way of the case-branch termination would lead to execution of the next case-branch of the switch statement, for example:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Snippet 3 #include <iostream> #include <stdexcept> void println(const Traffic_light_color value) { switch (value) { case Traffic_light_color::red: std::cout << "red\n"; // (1) case Traffic_light_color::yellow: std::cout << "yellow\n"; // (2) case Traffic_light_color::green: std::cout << "green\n"; // (3) } throw std::logic_error{"bug detected"}; // (4) } |
In any case, the function println() above will throw the object of type std::logic_error, since the line (4) are executed after the switch statement in the end of call. But depending on the value the different sequences of lines of the code will be executed:
- if
value == Traffic_light_color::redthen(1),(2),(3); - if
Traffic_light_color::yellowthen(2),(3); - if
Traffic_light_color::greenthen(3).
This situation demonstrates the fall-through the case labels. Fortunately, compilers like Embarcadero RAD Studio C++ can emit a warning if case-branches are not terminated somehow, since unintentional fall-through it’s usually indicates the programmer’s mistake. In order to prevent this behavior (and also the compiler warnings) the most suitable way of case-branch termination here is usage of the return statements to terminate each case-branch:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Snippet 4 #include <iostream> #include <stdexcept> void println(const Traffic_light_color value) { switch (value) { case Traffic_light_color::red: std::cout << "red\n"; return; case Traffic_light_color::yellow: std::cout << "yellow\n"; return; case Traffic_light_color::green: std::cout << "green\n"; return; } throw std::logic_error{"bug detected"}; } |
Of course, there are cases in which a fall-through is intentional, for instance, when the same case-branch is used to handle the range of values:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// Snippet 5 #include <iostream> #include <stdexcept> void println(const Traffic_light_color value) { switch (value) { case Traffic_light_color::red: // [[fallthrough]]; // legal attribute since C++17 case Traffic_light_color::yellow: std::cout << "the movement is forbidden\n"; return; case Traffic_light_color::green: std::cout << "the movement is permitted\n"; return; } throw std::logic_error{"bug detected"}; } |
Please note, that C++17 provides the special attribute [[fallthrough]] which should be used for both to explicitly express that the fall-through is intentional and to prevent a compiler to emit a warning.
Beware, that the default case-branch should never be used in the switch statements where each case-branch is used to handle a value of a enum type. Otherwise, the compiler will never emit the warning about the unspecified case-branches, for example:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Snippet 6 #include <iostream> void println(const Traffic_light_color value) { switch (value) { case Traffic_light_color::red: // [[fallthrough]]; // legal attribute since C++17 case Traffic_light_color::green: std::cout << "the movement is permitted\n"; break; default: throw std::logic_error{"bug detected"}; // silences the compiler } } |
In the example above the case to handle Traffic_light_color::yellow value is missed, but the presence of default case-branch signals the compiler that this is intentional and the compiler is forced to keep silence.
As with if statements (see If Statements in Modern C++) it’s possible to introduce variables into the scope of switch statements:
|
1 2 3 |
switch (const auto value = get_value()) { // Variable value is avaiable to all the case-branches. } |
Finally, since C++17 it’s possible to specify the expression that yields a value used for comparing with a set of constants of case-branches just after the variable declared in the scope of switch statement, for example:
|
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> #include <string_view> int main() { switch (const std::string_view ide{"Embarcadero RAD Studio is a powerful IDE!"}; ide.size()) { case 10: throw "nonsense"; default: std::cout << "The size of string \"" << ide << "\" is " << ide.size() << ".\n"; } } |



