C++11 introduced the constexpr
keyword for defining a constant expression.
A constant expression is a variable or function that may be evaluated at compile time. This has many uses, including extending a switch statement to support full strings.
C++ only supports using an integer as the condition in a switch
statement and an integer that is known at compile time in a case
statement.
You can define a hash function and use it to convert a string to an integer to use in a switch
statement.
If you define that hash function as a constexpr
you can use it to convert a string literal to an integer to use in a case
statement as well.
I'll use Daniel J. Bernstein's "Times 33 with Addition" hashing algorithm (e.g. DJBX33A) as an example. It is one of the fastest and most well distributed string hashing algorithms to date. It's implementation is also tiny:
Hash(input): return *input ? *input + 33 * Hash(input + 1) : 5381;
Here is the same algorithm defined as a templated C++ constant expression function:
template <typename _T>
unsigned int constexpr Hash(_T const* input) {
return *input ? static_cast<unsigned int>(*input) + 33 * Hash(input + 1) : 5381;
}
You can use the above hash function to supply a full string as the expression in a switch
or case
statement instead of just integers.
The input to Hash
in a case
statement needs to be a string literal.
// Example 1: Using with a char pointer
char* userInput{ GetUserInput() };
switch (Hash(userInput)) {
case Hash("match 1"): RunCode(); break;
case Hash("match 2"): RunMoreCode(); break;
default: break;
}
free(userInput);
// Example 2: Using with an std::string
std::string userInput{ GetUserInput() };
switch (Hash(userInput.data())) {
case Hash("match 1"): RunCode(); break;
case Hash("match 2"): RunMoreCode(); break;
default: break;
}
You can also use the above examples with a wchar_t
pointer or std::wstring
as well by using a wide character string literal in the case statement (e.g. Hash(L"match 1")
).
A constant expression will be evaluated by the compiler if the result of the expression needs to be known at compile time.
By providing a string literal to Hash
in the case
statement, the compiler will evaluate the expression and only store the resultant integer in the compiled code.
The input to Hash
in the switch
statement will not be known at compile time and the switch
statement will be evaluated at runtime.
A constant expression hash function provides an easy way to use full strings in a switch statement that is both fast and small.
Hashing strings at compile time prevents storing potentially sensitive strings in the .data
or .rdata
sections of an executable.
This can also be used in position indepenedent code or shellcode because the result of Hash
in the case
statement will be stored directly in the .text
section of the executable.