This article aims to show that switching on string values and matching them with string-valued case labels is both practical and possible in Modern C++ (since C++17), with a minimal amount of boilerplate code being necessary. The C++ switch statement is almost unchanged since the days of C, apart from the availability of the [[fallthrough]];
attribute to allow a non-empty case to fall through to the next, and the possibility of preceding the switch expression with an initializer. Importantly, case labels can still be only of integral type (from char
up to long long
).
The steps needed are as follows:
- Create a hashing function that generates an integral hash from a string (we’ll use C++17’s
std::string_view
as the source type) - Make sure this function can be declared
constexpr
(again, this needs C++17) - Write an
operator""
function (againconstexpr
) which calls the hashing function
Unfortunately, we can’t use std::hash
from the C++ Standard Library (its operator()
is not declared constexpr
). This means that a first attempt at Step 3 is not (yet) valid C++:
constexpr inline auto operator ""_sh (const char *str, size_t len) {
return std::hash<std::string>{}(std::string(str, len));
} // error: call of undefined function or one not declared 'constexpr'
So, going back to Steps 1 and 2, the following function will generate a suitable (32-bit) hash, using the popular (and fast) djb2a algorithm:
inline constexpr auto hash_djb2a(const std::string_view sv) {
unsigned long hash{ 5381 };
for (unsigned char c : sv) {
hash = ((hash << 5) + hash) ^ c;
}
return hash;
}
This means we can now use switch(hash_djb2a(SVAR)) { …
in our code, where SVAR
is a std::string
, const char *
or std::string_view
.
Revisiting Step 3, we can now write a valid operator""
:
inline constexpr auto operator"" _sh(const char *str, size_t len) {
return hash_djb2a(std::string_view{ str, len });
}
With this function defined we can now write case "Label"_sh:
in our code, using any different text instead of "Label"
(which will be matched case-sensitively). By the way, names for operator""
starting with underscore, as for our use of _sh
, are guaranteed exempt from standardization and can be used freely. Below is a complete, working program which puts these ideas together:
#include <string_view>
#include <iostream>
#include <string>
inline constexpr auto hash_djb2a(const std::string_view sv) {
unsigned long hash{ 5381 };
for (unsigned char c : sv) {
hash = ((hash << 5) + hash) ^ c;
}
return hash;
}
inline constexpr auto operator"" _sh(const char *str, size_t len) {
return hash_djb2a(std::string_view{ str, len });
}
int main() {
for (;;) {
std::cout << "Please enter one of: new, load, save, or quit:\n";
std::string option;
std::cin >> option;
switch(hash_djb2a(option)) {
case "new"_sh:
std::cout << "You entered \'new\'\n";
break;
case "load"_sh:
std::cout << "You entered \'load\'\n";
break;
case "save"_sh:
std::cout << "You entered \'save\'\n";
break;
case "quit"_sh:
std::cout << "You entered \'quit\'\n";
return 0;
default:
std::cout << "Command not recognized!\n";
break;
}
}
}
In this example main program, input of a single word to a std::string
is tested against four case
labels, each in the format as string-literal-with-literal-suffix. (For strings with spaces within, you would need to use getline()
.) Within the code for these case
labels, break;
and return;
work exactly as expected. Note that the risk of a hash collision (different literals resulting in the same hash) is small, but if it were to happen the compiler would always warn of a duplicate case
label value.
Of course, feel free to adapt and extend this technique in your own code. An alternative method of allowing switching on std::string
s and other non-integral types is to use the uberswitch library, which uses template metaprogramming (TMP) techniques. Yet another possibility is the Mach7 library, which allows more advanced pattern matching.
Update: 2021/03/14: As this page is the most popular on the site, I thought I’d try and improve the example code a little, as well as do some crystal-ball gazing.
Update: 2022/01/15: Changed the code slightly to use a real hashing algorithm (djb2a).
Resources: Download or browse the source code