C++ 如何测试constexpr的计算是否正确

C++ 如何测试constexpr的计算是否正确,c++,c++11,constexpr,C++,C++11,Constexpr,我使用constexpr在编译时计算哈希代码。代码编译正确,运行正确。但我不知道哈希值是编译时还是运行时。如果我在运行时跟踪代码,我不会将其转换为constexpr函数。但是,即使对于运行时值(计算运行时生成的字符串的哈希-相同的方法),也无法跟踪这些值。 我曾试图调查此事,但我完全不明白 出于调试目的,我的哈希代码仅为字符串长度,使用以下方法: constexpr inline size_t StringLengthCExpr(const char * const str) noexcept

我使用constexpr在编译时计算哈希代码。代码编译正确,运行正确。但我不知道哈希值是编译时还是运行时。如果我在运行时跟踪代码,我不会将其转换为constexpr函数。但是,即使对于运行时值(计算运行时生成的字符串的哈希-相同的方法),也无法跟踪这些值。 我曾试图调查此事,但我完全不明白

出于调试目的,我的哈希代码仅为字符串长度,使用以下方法:

constexpr inline size_t StringLengthCExpr(const char * const str) noexcept
{
    return (*str == 0) ? 0 : StringLengthCExpr(str + 1) + 1;
};
我创建了这样的ID类

class StringID
{
    public:
       constexpr StringID(const char * key);
    private:
       const unsigned int hashID;
}

constexpr inline StringID::StringID(const char * key)
        : hashID(StringLengthCExpr(key))
{

}
如果我在程序
main
方法中执行此操作

StringID id("hello world"); 
我得到了这段反汇编代码(其中的一部分——主要是内联方法和其他东西)

我怎么能从中判断“散列值”是一个编译时。我没有看到像11这样的常数被移动到寄存器。我不太擅长ASM,所以可能它是正确的,但我不确定要检查什么或者如何确定“哈希代码”值是编译时的,而不是在运行时根据该代码计算的

(我使用Visual Studio 2013 +英特尔C++ 15编译器-VS编译器不支持CONTXEPR)< /P> 编辑:

如果我更改代码并执行此操作

    const int ix = StringLengthCExpr("hello world");

    mov       DWORD PTR [-24+ebp], 11                       ;55.15
我得到了正确的结果

即使这样

将私有hashID更改为公共hashID

 StringID id("hello world"); 
  // mov       DWORD PTR [-24+ebp], 11                       ;55.15

 printf("%i", id.hashID);
  // some other ASM code
但是如果我使用私有hashID并添加Getter

  inline uint32 GetHashID() const { return this->hashID; };
到身份证课,然后我得到了

  StringID id("hello world"); 
  //see original "wrong" ASM code

  printf("%i", id.GetHashID());
  // some other ASM code

如果要确保在编译时计算
constepr
函数,请在需要编译时计算的内容中使用其结果:

template <size_t N>
struct ForceCompileTimeEvaluation { static constexpr size_t value = N; };

constexpr inline StringID::StringID(const char * key)
        : hashID(ForceCompileTimeEvaluation<StringLength(key)>::value)
{}
模板
struct ForceCompileTimeEvaluation{static constexpr size_t value=N;};
constexpr内联StringID::StringID(const char*key)
:hashID(ForceCompileTimeEvaluation::value)
{}

请注意,我已将函数重命名为just
StringLength
。名称以下划线开头,后跟大写字母,或包含两个连续下划线,在用户代码中不合法。它们是为实现(编译器和标准库)保留的。

最方便的方法是在
静态断言
语句中使用
constepr
。如果在编译时未对代码进行求值,则代码不会编译,而
static_assert
表达式在运行时不会给您带来任何开销(也不会像使用模板解决方案那样生成不必要的代码)

例如:

static_assert(_StringLength("meow") == 4, "The length should be 4!");

这还会检查函数是否正确计算结果。

有几种方法可以强制编译时计算。但这些并不像使用
constexpr
时所期望的那样灵活和容易设置。它们也不能帮助您发现编译时常量是否被实际使用

对于
constexpr
,您希望的是在您认为有益的地方工作。因此,您尝试满足其要求。但是,您需要测试预期在编译时生成的代码是否已生成,以及用户是否实际使用生成的结果或在运行时触发函数


我发现了两种方法来检测类或(成员)函数是否使用编译时或运行时计算路径

  • 使用
    constexpr
    函数的属性,如果在编译时求值,则从noexcept运算符(
    bool noexcept(expression)
    )返回
    true
    。因为生成的结果将是编译时常量。这种方法在单元测试中非常容易使用。
    (请注意,显式标记这些函数
    noexcept
    将中断测试。)

  • 由于noexcept运算符始终为常量表达式返回true,因此可以使用它检查constexpr函数的特定调用是否采用常量表达式分支(…)

  • (不太方便)使用调试器:在标记为
    constexpr
    的函数中放置断点。只要未触发断点,就会使用编译器计算的结果。这不是最简单的,但可能是偶然检查

  • 注意:在VisualStudio调试器中,您可以通过在constexpr函数中放置断点来判断它是否在编译时被计算。如果命中断点,则在运行时调用该函数。如果不是,则在编译时调用该函数


    在使用
    constexpr
    进行实验时,我发现这两种方法都很有用。尽管我没有在VS2017之外的环境中进行任何测试。并且在当前的标准草案中找不到支持此行为的明确声明。

    以下技巧有助于检查constexpr函数是否仅在编译时计算:

    使用gcc,您可以使用汇编清单+c源代码编译源文件;假设constexpr及其调用都在源文件try.cpp中

     gcc -std=c++11 -O2 -Wa,-a,-ad  try.cpp  | c++filt >try.lst  
    
    如果在运行时对constexpr函数进行了求值,那么您将在程序集列表try.lst中看到编译后的函数和调用指令(x86上的call function_name)(请注意,c++filt命令已取消修饰链接器名称)


    有趣的是,如果编译时没有优化(没有-O2或-O3选项),我总是会看到调用。

    只需将其放入constexpr变量中即可

    constexpr StringID id("hello world"); 
    constexpr int ix = StringLengthCExpr("hello world");
    
    constexpr变量始终是实常量表达式。如果它进行编译,则在编译时进行计算。

    将来(c++20)可以使用说明符声明函数,该函数必须在编译时进行计算,因此需要上下文

    consteval说明符将函数或函数模板声明为 立即函数,即对该函数的每次调用都必须 (直接或间接)生成编译时常量表达式

    cppreference中的示例(请参阅):


    在一个不相关的注释中,以下划线和大写字母开头的符号是保留的。您是否打开了optimi
    constexpr StringID id("hello world"); 
    constexpr int ix = StringLengthCExpr("hello world");
    
    consteval int sqr(int n) {
      return n*n;
    }
    constexpr int r = sqr(100);  // OK
    
    int x = 100;
    int r2 = sqr(x);  // Error: Call does not produce a constant
    
    consteval int sqrsqr(int n) {
      return sqr(sqr(n)); // Not a constant expression at this point, but OK
    }
    
    constexpr int dblsqr(int n) {
      return 2*sqr(n); // Error: Enclosing function is not consteval and sqr(n) is not a constant
    }