Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to get hash values as compile-time constants?

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.

like image 304
Zebrafish Avatar asked Feb 20 '18 23:02

Zebrafish


2 Answers

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

like image 85
Justin Avatar answered Oct 21 '22 16:10

Justin


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;
}
like image 26
Jive Dadson Avatar answered Oct 21 '22 14:10

Jive Dadson