我如何保证一个 核心常数 (如[expr.const].2中的)可能包含constexpr函数调用,在编译时是否会被实际执行,这取决于哪些条件?
constexpr
隐含地承诺通过将计算移入DeepL 翻译(编译时)来提高运行时性能。这两点似乎是相互矛盾的。
在什么情况下可以 依靠 编译器在编译时解析一个核心常量表达式(可能包含一个任意复杂的计算),而不是将其推迟到运行时?
至少在 -O0
gcc似乎真的发出代码并调用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) {}
}
当一个 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;
}
当编译时 -O0
,gcc输出以下汇编 main
:
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 55
mov eax, 55
pop rbp
ret
尽管所有的优化都被关闭了,但从来没有任何调用到 fib
在 main
函数本身。
这适用于所有的方式来追溯到 C++11
然而,在C++11中 fib
函数必须重新编写,以使用转换来避免使用可变变量。
为什么编译器包含了 fib
在可执行文件中,有时? A 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_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));
}
从来没有;C++标准允许几乎整个编译过程都在 "运行时 "进行。 有些诊断必须在编译时进行,但没有什么能阻止编译器的疯狂。
你的二进制文件可以是一个附加了你的源代码的编译器副本,而C++不会说编译器做错了什么。
你所看到的是一个QoI--Implrmentation的质量问题。
在实践中。constexpr
变量往往是在编译时计算的,而模板参数总是在编译时计算的。
consteval
也可以用来标记函数。