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