是否可以获取哈希值作为编译时常量?

问题描述 投票:0回答:5

我想我会尝试通过散列它们来选择不同的选项作为字符串,但这不起作用:

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

我似乎无法在编译时获取哈希值。根据我的阅读,每个不同的输入每次都应该产生相同的唯一输出,并且冲突的可能性非常低。考虑到这些属性,不能在编译时计算哈希值吗?我对散列不太了解,我通常使用 unordered_map,但为了学习我想尝试一些新的东西。

c++ hash compile-time-constant
5个回答
13
投票

std::hash::operator()
不是
constexpr
,所以你不能只使用它。相反,您必须编写自己的
constexpr
哈希函数。例如,以下是FNV-1a哈希算法(未经测试):

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

然后就可以使用它了:

int selectMenuOptionString(const std::string& str)
{
    switch (hashString(str))
    {
    case hashString(std::string_view("Selection one")): return 42; 
    default: return 0;
    }
}

请注意,如果您编写了

hashString("Selection one")
,它实际上也会对空终止符进行哈希处理,因此您可能需要重载来捕获字符串文字,例如:

template <size_t N>
constexpr size_t hashString(char const (&toHash)[N])
{
    return hashString(std::string_view(toHash));
}

演示


4
投票

您需要实现自己的哈希函数,因为 constexpr 没有合适的 std::hash 实例化。 这是一个便宜又肮脏的...

编辑:为了不被 Justin 的回答羞辱得太严重,我添加了一个 32 位分支。

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

0
投票

我只是想添加这个,因为我觉得它很酷。我从这里的一个问题中得到的 constexpr strlen: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");
}

0
投票

这是 Justin 答案的一个版本,它也使用 适当的 FNV 哈希参数 处理 32 位(如 Jive Dadson 答案所暗示的):

template <typename Str>
constexpr size_t hashString(const Str& toHash)
{
    // Based on 
    static_assert(sizeof(size_t) == 8 || sizeof(size_t) == 4);
    // fnv-1
    size_t result = []{
        if constexpr(sizeof(size_t) == 8) return 0xcbf29ce484222325;
        else return 0x811c9dc5;
    }();
    const size_t prime = []{
        if constexpr(sizeof(size_t) == 8) return 0x00000100000001b3;
        else return 0x01000193; 
    }();

    for (char c : toHash) {
        result *= prime;
        result ^= c;
    }
    return result;
}

值得注意的是,如果您需要不区分大小写的基本版本,只需将 XOR 行更改为:

    result ^= std::tolower(c);

您可能需要

#include <cctype>
。此版本将受到
std::tolower
不能很好地处理非 ASCII 编码的所有限制。


-1
投票

你无法在编译时获取运行时值的哈希值,不。

即使您传递了

std::hash
一个常量表达式,它也未被定义为能够在编译时执行其哈希工作。

据我所知(这并不远),你必须想出一些可怕的模板元黑客(或者更糟糕的是宏!)才能做到这一点。就我个人而言,如果您的文本输入在构建时已知,我只需在代码外部预生成一个哈希值,也许是在某些 Python 驱动的预构建步骤中。

© www.soinside.com 2019 - 2024. All rights reserved.