什么时候在编译时评估 constexpr?
When is a constexpr evaluated at compile time?
我有什么保证 核心常量表达式(如 [expr.const].2)可能包含 constexpr 函数调用实际上将在编译时求值这取决于哪些条件?
constexpr
的引入隐含地承诺通过将计算移至翻译阶段(编译时)来提高运行时性能。
- 但是,该标准没有(而且大概不能)强制要求编译器生成什么代码。 (参见 [expr.const] 和 [dcl.constexpr])。
这两点似乎相互矛盾。
在什么情况下可以依赖编译器在编译时解析核心常量表达式(可能包含任意复杂的计算)而不是推迟它到运行时?
至少在 -O0
下,gcc 似乎实际发出代码并调用 constexpr 函数。 -O1
及以上没有。
我们是否必须求助于 trickery 这样的强制 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
尽管所有优化都已关闭,但 main
函数本身从未调用过 fib
。
这适用于一直追溯到 C++11
,但是在 C++11 中,必须重写 fib
函数以使用转换来避免使用 mutable 个变量。
为什么编译器有时会在 executable 中包含 fib
的程序集? 一个 constexpr
函数 可以 在 运行 时使用,并且在 运行 时调用时它的行为将像常规函数一样。
如果使用得当,constexpr
可以在特定情况下提供一些性能优势,但推动一切 constexpr
更多的是编写编译器可以检查未定义行为的代码。
什么是 constexpr
提供性能优势的示例? 在实现像 std::visit
这样的函数时,您需要创建一个查找 table的函数指针。每次调用 std::visit
时都创建查找 table 的成本很高,并且将查找 table 分配给 static
局部变量仍会导致可衡量的开销,因为程序必须每次函数 运行 时检查该变量是否已初始化。
值得庆幸的是,您可以进行查找 table constexpr
,编译器实际上会 将查找 table 内联到函数的汇编代码中 ,因此当 std::visit
为 运行 时,查找 table 的内容更有可能位于指令缓存内。
C++20 是否提供任何机制来保证某些东西在编译时 运行s?
如果一个函数是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++ 标准允许几乎整个编译发生在 "runtime"。一些诊断必须在编译时完成,但没有什么能阻止编译器的疯狂。
您的二进制文件可能是编译器的副本,附加了您的源代码,C++ 不会说编译器做错了什么。
您正在查看的是 QoI - 实施质量 - 问题。
实际上,constexpr
变量往往是在编译时计算的,而模板参数总是在编译时计算的。
consteval
也可用于标记函数。
我有什么保证 核心常量表达式(如 [expr.const].2)可能包含 constexpr 函数调用实际上将在编译时求值这取决于哪些条件?
constexpr
的引入隐含地承诺通过将计算移至翻译阶段(编译时)来提高运行时性能。- 但是,该标准没有(而且大概不能)强制要求编译器生成什么代码。 (参见 [expr.const] 和 [dcl.constexpr])。
这两点似乎相互矛盾。
在什么情况下可以依赖编译器在编译时解析核心常量表达式(可能包含任意复杂的计算)而不是推迟它到运行时?
至少在 -O0
下,gcc 似乎实际发出代码并调用 constexpr 函数。 -O1
及以上没有。
我们是否必须求助于 trickery 这样的强制 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
尽管所有优化都已关闭,但 main
函数本身从未调用过 fib
。
这适用于一直追溯到 C++11
,但是在 C++11 中,必须重写 fib
函数以使用转换来避免使用 mutable 个变量。
为什么编译器有时会在 executable 中包含 fib
的程序集? 一个 constexpr
函数 可以 在 运行 时使用,并且在 运行 时调用时它的行为将像常规函数一样。
如果使用得当,constexpr
可以在特定情况下提供一些性能优势,但推动一切 constexpr
更多的是编写编译器可以检查未定义行为的代码。
什么是 constexpr
提供性能优势的示例? 在实现像 std::visit
这样的函数时,您需要创建一个查找 table的函数指针。每次调用 std::visit
时都创建查找 table 的成本很高,并且将查找 table 分配给 static
局部变量仍会导致可衡量的开销,因为程序必须每次函数 运行 时检查该变量是否已初始化。
值得庆幸的是,您可以进行查找 table constexpr
,编译器实际上会 将查找 table 内联到函数的汇编代码中 ,因此当 std::visit
为 运行 时,查找 table 的内容更有可能位于指令缓存内。
C++20 是否提供任何机制来保证某些东西在编译时 运行s?
如果一个函数是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++ 标准允许几乎整个编译发生在 "runtime"。一些诊断必须在编译时完成,但没有什么能阻止编译器的疯狂。
您的二进制文件可能是编译器的副本,附加了您的源代码,C++ 不会说编译器做错了什么。
您正在查看的是 QoI - 实施质量 - 问题。
实际上,constexpr
变量往往是在编译时计算的,而模板参数总是在编译时计算的。
consteval
也可用于标记函数。