I thought I'd try selecting different options as strings by hashing them, but this doesn't work:
#include <type_traits>
#include <string>
inline void selectMenuOptionString(const std::string& str)
{
switch (std::hash<std::string>()(str))
{
case std::hash<std::string>()(std::string("Selection one")) : break;
// Expression must have a constant value
}
}
inline void selectMenuOptionString2(const std::string& str)
{
size_t selectionOneHash = std::hash<std::string>()(std::string("Selection one"));
switch (std::hash<std::string>()(str))
{
case selectionOneHash: // Expression must have a constant value
// The variable of selectionOneHash cannot be used as a constant
}
constexpr size_t hash = std::hash<int>()(6); // Expression must have a constant value
}
It seems I can't get hash values at compile time. From what I've read each different input should yield the same unique output every time, with a very low chance of collision. Given these properties couldn't the hash value be calculated at compile time? I don't know much at all about hashing, I usually use an unordered_map, but I wanted to try something new for learning's sake.
std::hash::operator()
isn't constexpr
, so you can't just use it. Instead, you'd have to write your own constexpr
hash function. For example, the following is the FNV-1a hash algorithm (untested):
template <typename Str>
constexpr size_t hashString(const Str& toHash)
{
// For this example, I'm requiring size_t to be 64-bit, but you could
// easily change the offset and prime used to the appropriate ones
// based on sizeof(size_t).
static_assert(sizeof(size_t) == 8);
// FNV-1a 64 bit algorithm
size_t result = 0xcbf29ce484222325; // FNV offset basis
for (char c : toHash) {
result ^= c;
result *= 1099511628211; // FNV prime
}
return result;
}
And then you can use it:
int selectMenuOptionString(const std::string& str)
{
switch (hashString(str))
{
case hashString(std::string_view("Selection one")): return 42;
default: return 0;
}
}
Note that if you wrote hashString("Selection one")
, it would actually hash the null terminator as well, so you might want to have an overload to catch string literals, such as:
template <size_t N>
constexpr size_t hashString(char const (&toHash)[N])
{
return hashString(std::string_view(toHash));
}
Demo
You'll need to implement your own hash function, because there's no suitable instantiation of std::hash that's constexpr. Here's a cheap-and-dirty...
EDIT: In order not to be humiliated too badly by Justin's answer, I added a 32 bit branch.
constexpr size_t hash(const char *str) {
static_assert(sizeof(size_t) == 8 || sizeof(size_t) == 4);
size_t h = 0;
if constexpr(sizeof(size_t) == 8) {
h = 1125899906842597L; // prime
} else {
h = 4294967291L;
}
int i = 0;
while (str[i] != 0) {
h = 31 * h + str[i++];
}
return h;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With