5

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?

4
  • 1
    I've used Python as a pre-preprocessor in a C++ program before, so that Python could generate code at compile time. I'd generate things like lookup tables. Could be used to precalculate the hashes very easily. This was before we had as much fancy C++ template compile time meta-programming. Commented Jan 13, 2024 at 20:30
  • 1
    @Eljay: sounds fancy, but not something I'd like to do. In the end, the function call function("string"); should be as simple and C++ alike as possible, so my hope is that some wizzard knows how to use the newest C++ thingies to accomplish that. My macro solution works, but already feels a bit 'iffy'. Commented Jan 13, 2024 at 20:44
  • 1
    Maybe create a "hashed string" struct with a constexpr constructor, and pass that instead? You could also define a user defined literal for it. Commented Jan 13, 2024 at 20:49
  • @Brian61354270: I'm not certain if the struct thing will work. What I understood is that it's difficult to have constexpr in all the right places to guarantee compile-time evaluation. I'll look into the string literals also, no idea how that works :-) Commented Jan 13, 2024 at 21:02

2 Answers 2

3

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

Sign up to request clarification or add additional context in comments.

1 Comment

This seems to work great! No call to HashLiteral() to be seen in the assembly...
3

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

1 Comment

For extra brevity, you could replace the call to HashLiteral with a user-defined literal like "string"_hashed. Example on godbolt: godbolt.org/z/3dKdKToM6

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.