【问题标题】:Compile time hash with constexpr使用 constexpr 编译时间哈希
【发布时间】:2017-10-22 09:00:50
【问题描述】:

我在一本用于在编译时创建 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;
}

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

【问题讨论】:

  • 对此我唯一能说的就是重复错误消息:您正在调用一个未标记为 constexpr 的函数。您的 CalculateCalculateValue 函数不是 constexpr。计算 constexpr 值时不能调用非 constexpr 函数。
  • 你可能想要const。该函数的值在编译时是未知的,因为您在运行时提供了一个参数。

标签: c++ c++11 hash c++14 template-meta-programming


【解决方案1】:

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

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

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

在赋值表达式内:

constexpr int eventID = SDBMCalculator<5>::CalculateValue("Hello");

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

那么我们有两个选择:

  • 只需将constexpr 更改为const

  • 或者尝试使CalculateValue成为constexpr函数

因为第一个真的很无聊,让我们专注于第二个更好 了解常量表达式的工作原理!

所以我们首先将CalculateValue 标记为constexpr

static constexpr inline int CalculateValue(const char* const stringToHash)

现在CalculateValue 必须只调用constexpr 函数。 所以我们也必须把Calculate 变成constexpr

static constexpr inline int Calculate(const char* const stringToHash, int& value)

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

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

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

嗯,差不多了。 我们还必须在SDBMCalculator&lt;1&gt; 中也将Calculate 设为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;
}

当然它不会编译! 我们得到:

错误:移位表达式 '(4723229

值=字符+(值

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

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

g++ example.cpp -o example.exe -fpermissive

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

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

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

【讨论】:

  • 感谢您的详细解答!太好了,我希望我可以选择两个答案。我觉得人们应该先阅读 CygnusX1 的答案,然后如果他们想要/需要更多背景信息来找你。
  • 注意溢出部分。错误的存在不是因为它的编译时间,而是因为在有符号整数上溢出 unsigned int。具有明确定义的行为并且在运行时和编译时都可以正常工作且没有任何错误/警告的溢出。
【解决方案2】:

阅读错误消息:在计算 constexpr 值时,您正在调用非 constexpr 函数。你试过解决这个问题吗?

当您将所有相关函数设为constexpr 时,您会收到一些需要注意的额外错误。一些备注:

  • 确保使用-std=c++14 进行编译。 C++11 还不够好。
  • SDBMCalculator 函数中删除对 std::cout 的所有操作 - 这些操作在编译时是不允许的
  • 在所有相关计算中将int 更改为unsigned int。当 int 类型的左移溢出时,您会得到未定义的行为。无符号类型的左移是对其最大值+1 取模计算的。

    error: shift expression ‘(4723229 << 16)’ overflows
    constexpr int eventID = SDBMCalculator<5>::CalculateValue("Hello")
    

通过上述所有修复,您的代码将正常工作。我得到了结果:

2873473298

【讨论】:

  • @Ron 使用带符号的 int 行为是未定义的。没有可更改的功能。
  • @n.m.我知道了。欣赏它。
  • 快速而痛苦的回答。谢谢。学到了一些东西。
猜你喜欢
  • 2022-01-19
  • 2014-05-09
  • 2017-01-01
  • 2018-05-07
  • 1970-01-01
  • 1970-01-01
  • 2018-01-18
  • 2014-01-22
  • 1970-01-01
相关资源
最近更新 更多