用constexpr编译时间哈希

时间:2017-10-22 09:00:50

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

我在一本书中找到了这个示例/类,用于在编译时创建SDBM哈希。不幸的是它没有编译(无论是c ++ 11还是c ++ 14)。我得到了error: call to non-constexpr function。我试过了一点,但我似乎无法做到这一点。所以这是我的问题:

  1. 为什么它不起作用,如何解决? (对不起,我知道这是一个普遍的问题,但至少对于一个非常具体的案例而言)
  2. 您要测试的完整(不工作)示例:

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

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

2 个答案:

答案 0 :(得分:1)

阅读错误消息:在评估constexpr值时,您正在调用非constexpr函数。你试过修理它吗?

当您将所有相关功能设为constexpr时,您将收到一些需要引起注意的其他错误。一些评论:

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

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

通过以上所有修复,您的代码将起作用。我得到了结果:

2873473298

答案 1 :(得分:1)

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

  

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

     

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

在assign表达式中:

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标记。 (operator<<就像一个普通函数,它不是constexpr)。

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

嗯,它几乎就在那里。 我们还必须在CalculateSDBMCalculator<1>中设置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;
}

当然它没有编译! 我们得到:

  

错误:shift表达式'(4723229&lt;&lt; 16)'溢出[-fpermissive]

     

value = character +(value&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt

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

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

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

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

这很好,因为我们不会浪费运行时资源,但是如果你使用大量的这样的模板和constexprs并使编译器计算所有将致命的慢速编译!

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