简体   繁体   中英

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.

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;
}

You can't get the hash of a runtime value at compile-time, no.

Even if you passed std::hash a constant expression, it is not defined to be able to do its hashing work at compile-time.

As far as I know (which isn't far), you'd have to come up with some monstrous template metahackery (or, worse, macros!) to do this. Personally, if your text input is known at build, I'd just pregenerate a hash outside of the code, perhaps in some Python-driven pre-build step.

I just wanted to add this because I think it's cool. The constexpr strlen I got from a question here: constexpr strlen

#include <iostream>
#include <string>

int constexpr strlength(const char* str)
{
    return *str ? 1 + strlength(str + 1) : 0;
}

size_t constexpr Hash(const char *first)
{   // FNV-1a hash function 
    const size_t FNVoffsetBasis = 14695981039346656037ULL;
    const size_t FNVprime = 1099511628211ULL;
    const size_t count = strlength(first);
    size_t val = FNVoffsetBasis;
    for (size_t next = 0; next < count; ++next)
    {
        val ^= (size_t)first[next];
        val *= FNVprime;
    }
    return val;
}

inline void selectMenuOptionString(const std::string& str)
{
    switch (Hash(str.c_str()))
    {
    case Hash("Selection one"): /*Do something*/ break;
    case Hash("Selection two"): /*Do something*/ break;
    }
}

int main()
{
    static_assert(strlength("Hello") == 5, "String length not equal");
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM