bit*_*ask 9 c++ compilation language-lawyer constexpr c++20
我有什么保证可能包含 constexpr 函数调用的核心常量表达式(如在 [expr.const].2 中)将在编译时实际评估,这取决于哪些条件?
constexpr通过将计算移至翻译阶段(编译时),引入隐式承诺提高运行时性能。这两点似乎相互矛盾。
在什么情况下可以依靠编译器在编译时解析核心常量表达式(可能包含任意复杂的计算)而不是将其推迟到运行时?
至少在-O0gcc下似乎实际上发出代码并调用 constexpr 函数。在-O1最多不。
难道我们不得不求助于挂羊头卖狗肉,如这个,那个力量通过模板系统constexpr:
template <auto V>
struct compile_time_h { static constexpr auto value = V; };
template <auto V>
inline constexpr auto compile_time = compile_time_h<V>::value;
constexpr int f(int x) { return x; }
int main() {
for (int x = 0; x < compile_time<f(42)>; ++x) {}
}
Run Code Online (Sandbox Code Playgroud)
当constexpr调用函数并将输出分配给constexpr变量时,它将始终在编译时运行。
这是一个最小的例子:
// Compile with -std=c++14 or later
constexpr int fib(int n) {
int f0 = 0;
int f1 = 1;
for(int i = 0; i < n; i++) {
int hold = f0 + f1;
f0 = f1;
f1 = hold;
}
return f0;
}
int main() {
constexpr int blarg = fib(10);
return blarg;
}
Run Code Online (Sandbox Code Playgroud)
在 编译时-O0,gcc 为 输出以下程序集main:
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 55
mov eax, 55
pop rbp
ret
Run Code Online (Sandbox Code Playgroud)
尽管关闭了所有优化,但函数本身从未调用fib过main。
这适用于一直回到C++11,但是在 C++11 中fib,必须重新编写该函数以使用转换以避免使用可变变量。
为什么编译器fib有时会在可执行文件中包含程序集?一个constexpr函数可以在运行时使用,当在运行时调用时,它的行为就像一个普通函数。
使用得当,constexpr可以在特定情况下提供一些性能优势,但推动一切constexpr更多的是编写编译器可以检查未定义行为的代码。
constexpr提供性能优势的示例是什么?在实现类似 的函数时std::visit,您需要创建一个函数指针查找表。每次std::visit调用时都创建查找表的成本很高,并且将查找表分配给static局部变量仍然会导致可测量的开销,因为每次运行函数时程序都必须检查该变量是否已初始化。
值得庆幸的是,您可以创建查找表constexpr,编译器实际上会将查找表内联到函数的汇编代码中,以便在运行时查找表的内容更有可能位于指令缓存中std::visit。
C++20 是否提供任何机制来保证某些东西在编译时运行?
如果函数是consteval,则标准规定每次调用该函数都必须生成一个编译时常量。
这可以简单地用于强制任何 constexpr 函数的编译时评估:
template<class T>
consteval T run_at_compiletime(T value) {
return value;
}
Run Code Online (Sandbox Code Playgroud)
作为参数给出的任何东西都run_at_compiletime必须在编译时评估:
constexpr int fib(int n) {
int f0 = 0;
int f1 = 1;
for(int i = 0; i < n; i++) {
int hold = f0 + f1;
f0 = f1;
f1 = hold;
}
return f0;
}
int main() {
// fib(10) will definitely run at compile time
return run_at_compiletime(fib(10));
}
Run Code Online (Sandbox Code Playgroud)
绝不; C++ 标准允许几乎整个编译发生在“运行时”。一些诊断必须在编译时完成,但没有什么可以阻止编译器的疯狂行为。
您的二进制文件可以是附加了源代码的编译器的副本,并且 C++ 不会说编译器做错了什么。
您看到的是 QoI(实施质量)问题。
实际上,constexpr变量往往是在编译时计算的,而模板参数总是在编译时计算的。
consteval也可用于标记函数。