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::red
then(1)
,(2)
,(3)
; - if
Traffic_light_color::yellow
then(2)
,(3)
; - if
Traffic_light_color::green
then(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"; } } |