Calculate 'const char *' string hash at compile-time

200 Views Asked by At

I have a function that calculates the hash of a string literal:

inline consteval uint64_t HashLiteral(const char* key)
{
   // body not important here...
   return 0;
}

In another function I need both the literal string, and its hash which I want to have calculated at compile-time:

void function(const char* s)
{
   worker(s, HashLiteral(s));
}

It seems however be impossible to make such a call like function("string") and have the hash calculated in its body at compile-time. The best I came up for now is using a macro, and redefine the function:

#define MakeHashParms(s)   s,HashLiteral(s)
void function(const char* s, const uint64_t hash)
{
   worker(s, hash);
}
function(MakeHashParms("string"));

Is a more straightforward solution possible?

2

There are 2 best solutions below

1
Brian61354270 On BEST ANSWER

You could modify function to accept a "hashed string" structure which can be implicitly constructed from a const char*. If you make the conversion constructor consteval, you can guarantee that the hash is always computed at compile time.

#include <cstdint>

inline consteval std::uint64_t HashLiteral(const char*)
{
   // body not important here...
   return 0;
}

struct HashedString {
    const char* str;
    std::uint64_t hash;

    consteval HashedString(const char* s)
        : str{s}, hash{HashLiteral(s)} {}
};

void worker(const char*, std::uint64_t) {};

void function(HashedString s)
{
    worker(s.str, s.hash);
}

int main() 
{
    function("string");  // OK: hash computed at compile time

    const char* x = "runtime string";
    // function(x);      // error: x is not constant expression
}

Try it online on godbolt

1
Super-intelligent Shade On

The problem is that the compiler has to generate runtime version of function() and there is no runtime version of HashLiteral() to do that.

The hash has to be calculated external to the function(), but you don't have to use macros for that. You can either use a struct as Brian suggested in his answer, or just make your HashLiteral() function bundle the original string with the hash.

For extra brevity (thanks Brian) and readability you could do this:

#include <cstdint>
#include <tuple>

consteval auto HashLiteral(const char* s)
{
    std::uint64_t hash = 0;
    // body not important here...
    return std::make_tuple(s, hash);
}

void worker(const char*, std::uint64_t)
{
    // do work
}

void function(const auto& sh)
{
    const auto& [s, hash] = sh;
    worker(s, hash);
}

consteval auto operator""_hashed(const char* s, std::size_t) { return HashLiteral(s); }

int main()
{
    function("string"_hashed);
}

Ref: https://godbolt.org/z/ssqoz91qs