return 语句中的 C++ constexpr 函数
C++ constexpr function in return statement
为什么 constexpr 函数不是在编译时求值,而是在运行时在 main 函数的 return 语句中求值?
它试过了
template<int x>
constexpr int fac() {
return fac<x - 1>() * x;
}
template<>
constexpr int fac<1>() {
return 1;
}
int main() {
const int x = fac<3>();
return x;
}
结果是
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 6
mov eax, 6
pop rbp
ret
使用 gcc 8.2。但是当我在 return 语句
中调用函数时
template<int x>
constexpr int fac() {
return fac<x - 1>() * x;
}
template<>
constexpr int fac<1>() {
return 1;
}
int main() {
return fac<3>();
}
我明白了
int fac<1>():
push rbp
mov rbp, rsp
mov eax, 1
pop rbp
ret
main:
push rbp
mov rbp, rsp
call int fac<3>()
nop
pop rbp
ret
int fac<2>():
push rbp
mov rbp, rsp
call int fac<1>()
add eax, eax
pop rbp
ret
int fac<3>():
push rbp
mov rbp, rsp
call int fac<2>()
mov edx, eax
mov eax, edx
add eax, eax
add eax, edx
pop rbp
ret
为什么第一个代码在编译时求值,第二个在运行时求值?
我还用 clang 7.0.0 尝试了这两个片段,它们在运行时进行了评估。为什么这对 clang 无效?
所有评估均在 Godbolt 编译器资源管理器中完成。
关于 constexpr
的一个常见误解是它意味着 "this will be evaluated at compile time"1.
不是。 constexpr
的引入是为了让我们编写自然代码, 可以 在需要它们的上下文中生成常量表达式。意思是"this must be evaluatable at compile time",就是编译器会检查的。
因此,如果您编写了一个返回 int 的 constexpr
函数,您可以使用它来计算一个模板参数,一个 constexpr
变量的初始值设定项(如果它是一个 const
整数类型)或数组大小。您可以使用该函数获得自然的、声明性的、可读的代码,而不是过去需要求助于旧的元编程技巧。
但是 constexpr
函数仍然是常规函数。 constexpr
说明符并不意味着编译器 有 2 来优化它并在编译时进行常量折叠。最好不要因为这样的提示而混淆。
1 - 感谢 user463035818 的措辞。
2 - c++20 和 consteval
是另一回事了:)
StoryTeller 的回答很好,但我认为可能会略有不同。
和constexpr
,分三种情况:
编译时上下文需要结果,例如数组大小。在这种情况下,参数也必须在编译时已知。在编译时评估是可能,至少所有可诊断的错误都会在编译时被发现。
参数仅在运行时已知,编译时不需要结果。在这种情况下,评估必须在 运行 时间发生。
参数可能在编译时可用,但只有在 运行 时才需要结果。
第四种组合(参数仅在运行时可用,编译时需要结果)错误;编译器将拒绝此类代码。
现在,在情况 1 和情况 3 中,计算可以在编译时进行,因为所有输入都可用。但是为了方便案例 2,编译器必须能够创建一个 运行 时间版本,并且它可能决定在其他情况下也使用这个变体 - 如果可以的话。
例如一些编译器在内部支持可变大小的数组,因此即使语言需要编译时数组边界,实现也可能决定不这样做。
为什么 constexpr 函数不是在编译时求值,而是在运行时在 main 函数的 return 语句中求值?
它试过了
template<int x>
constexpr int fac() {
return fac<x - 1>() * x;
}
template<>
constexpr int fac<1>() {
return 1;
}
int main() {
const int x = fac<3>();
return x;
}
结果是
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 6
mov eax, 6
pop rbp
ret
使用 gcc 8.2。但是当我在 return 语句
中调用函数时template<int x>
constexpr int fac() {
return fac<x - 1>() * x;
}
template<>
constexpr int fac<1>() {
return 1;
}
int main() {
return fac<3>();
}
我明白了
int fac<1>():
push rbp
mov rbp, rsp
mov eax, 1
pop rbp
ret
main:
push rbp
mov rbp, rsp
call int fac<3>()
nop
pop rbp
ret
int fac<2>():
push rbp
mov rbp, rsp
call int fac<1>()
add eax, eax
pop rbp
ret
int fac<3>():
push rbp
mov rbp, rsp
call int fac<2>()
mov edx, eax
mov eax, edx
add eax, eax
add eax, edx
pop rbp
ret
为什么第一个代码在编译时求值,第二个在运行时求值?
我还用 clang 7.0.0 尝试了这两个片段,它们在运行时进行了评估。为什么这对 clang 无效?
所有评估均在 Godbolt 编译器资源管理器中完成。
关于 constexpr
的一个常见误解是它意味着 "this will be evaluated at compile time"1.
不是。 constexpr
的引入是为了让我们编写自然代码, 可以 在需要它们的上下文中生成常量表达式。意思是"this must be evaluatable at compile time",就是编译器会检查的。
因此,如果您编写了一个返回 int 的 constexpr
函数,您可以使用它来计算一个模板参数,一个 constexpr
变量的初始值设定项(如果它是一个 const
整数类型)或数组大小。您可以使用该函数获得自然的、声明性的、可读的代码,而不是过去需要求助于旧的元编程技巧。
但是 constexpr
函数仍然是常规函数。 constexpr
说明符并不意味着编译器 有 2 来优化它并在编译时进行常量折叠。最好不要因为这样的提示而混淆。
1 - 感谢 user463035818 的措辞。
2 - c++20 和 consteval
是另一回事了:)
StoryTeller 的回答很好,但我认为可能会略有不同。
和constexpr
,分三种情况:
编译时上下文需要结果,例如数组大小。在这种情况下,参数也必须在编译时已知。在编译时评估是可能,至少所有可诊断的错误都会在编译时被发现。
参数仅在运行时已知,编译时不需要结果。在这种情况下,评估必须在 运行 时间发生。
参数可能在编译时可用,但只有在 运行 时才需要结果。
第四种组合(参数仅在运行时可用,编译时需要结果)错误;编译器将拒绝此类代码。
现在,在情况 1 和情况 3 中,计算可以在编译时进行,因为所有输入都可用。但是为了方便案例 2,编译器必须能够创建一个 运行 时间版本,并且它可能决定在其他情况下也使用这个变体 - 如果可以的话。
例如一些编译器在内部支持可变大小的数组,因此即使语言需要编译时数组边界,实现也可能决定不这样做。