使用 constexpr 编译时哈希

llh*_*llh 4 c++ hash template-meta-programming c++11 c++14

我在一本用于在编译时创建 SDBM 哈希值的书中找到了这个示例/类。不幸的是它无法编译(无论是 c++11 还是 c++14)。我正进入(状态error: call to non-constexpr function。我尝试了一下,但似乎无法完成这项工作。所以这是我的问题:

  1. 为什么它不起作用以及如何修复它?(很抱歉,我知道这是一个通用问题,但至少对于一个非常具体的情况)

完整(不起作用)示例供您测试:

#include <iostream>

template <int stringLength>
struct SDBMCalculator
{
    static inline int Calculate(const char* const stringToHash, int& value)
    {
            int character = SDBMCalculator<stringLength - 1>::Calculate(stringToHash, value);
            value = character + (value << 6) + (value << 16) - value;
            std::cout << static_cast<char>(character) << std::endl << value << std::endl << std::endl;
            return stringToHash[stringLength - 1];
    }

    static inline int CalculateValue(const char* const stringToHash)
    {
            int value = 0;
            int character = SDBMCalculator<stringLength>::Calculate(stringToHash, value);
            value = character + (value << 6) + (value << 16) - value;
            std::cout << static_cast<char>(character) << std::endl << value << std::endl << std::endl;
            return value;
    }
};

template <>
struct SDBMCalculator<1>
{
    static inline int Calculate(const char* const stringToHash, int& value)
    {
            return stringToHash[0];
    }
};


int main()
{
  constexpr int eventID = SDBMCalculator<5>::CalculateValue("Hello");
  std::cout << eventID << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

预先非常感谢您的时间和精力!

Pio*_*ski 5

正如所说http://en.cppreference.com

constexpr 变量必须满足以下要求:

其初始化的完整表达式,包括所有隐式转换、构造函数调用等,必须是常量表达式

在赋值表达式中:

constexpr int eventID = SDBMCalculator<5>::CalculateValue("Hello");
Run Code Online (Sandbox Code Playgroud)

我们使用CalculateValue的是没有用constexpr标记的。

那么我们有两个选择:

  • 只需更改constexprconst

  • 或者尝试做CalculateValue一个constexpr函数

由于第一个确实很无聊,让我们关注第二个,以更好地理解常量表达式是如何工作的!

所以我们从标记CalculateValue为开始constexpr

static constexpr inline int CalculateValue(const char* const stringToHash)
Run Code Online (Sandbox Code Playgroud)

现在CalculateValue必须仅调用constexpr函数。所以我们也必须做Calculate一个constexpr

static constexpr inline int Calculate(const char* const stringToHash, int& value)
Run Code Online (Sandbox Code Playgroud)

这会引发很多编译器错误。

幸运的是,我们可以注意到,将流放入 constexpr 中并不是一件好事,因为流上的操作没有用 constexpr 标记。(operator<<就像一个普通函数,它不是 constexpr )。

所以让我们从那里删除std::cout

好吧,差不多就到了。我们还必须CalculateSDBMCalculator<1>a中进行 make constexpr,因为它可能会被两个计算函数调用。

最终代码如下所示:

#include <iostream>

template <int stringLength>
struct SDBMCalculator
{
    static constexpr inline int Calculate(const char* const stringToHash, int& value)
    {
            int character = SDBMCalculator<stringLength - 1>::Calculate(stringToHash, value);
            value = character + (value << 6) + (value << 16) - value;
            return stringToHash[stringLength - 1];
    }

    static constexpr inline int CalculateValue(const char* const stringToHash)
    {
            int value = 0;
            int character = SDBMCalculator<stringLength>::Calculate(stringToHash, value);
            value = character + (value << 6) + (value << 16) - value;
            return value;
    }
};

template <>
struct SDBMCalculator<1>
{
    static constexpr inline int Calculate(const char* const stringToHash, int& value)
    {
            return stringToHash[0];
    }
};


int main()
{
  constexpr int eventID = SDBMCalculator<5>::CalculateValue("Hello");
  std::cout << eventID << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

当然它无法编译! 我们得到:

错误:移位表达式 '(4723229 << 16)' 溢出 [-fpermissive]

值 = 字符 + (值 << 6) + (值 << 16) - 值;

这是因为编译器不希望常量表达式出现溢出。

-fpermissive我们可以在编译代码时添加标志来不安全地忽略此错误。

g++ example.cpp -o example.exe -fpermissive
Run Code Online (Sandbox Code Playgroud)

现在它可以编译并且工作得很好!常量表达式修饰符使编译器在编译时计算哈希值。

这很好,因为我们不会为此浪费运行时资源,但如果您使用大量此类模板和 constexpr 并让编译器计算所有这些,那么编译速度将非常慢!

我希望你constexpr现在能更好地理解行为:)

  • 注意溢出部分。错误的出现并不是因为它的编译时间,而是因为有符号整数上溢出 &lt;&lt; 是一种未定义的行为。constexpr 只是允许编译器在编译时检测 UB。你真正应该做的是使用“unsigned int”来代替。溢出具有明确定义的行为,并且在运行时和编译时都有效,没有任何错误/警告。 (3认同)