据称,std::abs
不是标准中的 constexpr
(即使在 C++20 中也是如此)。但在实践中我发现我可以在函数模板化的非常特殊的条件下将其编译为 constexpr 。请参阅这个完整的工作示例:
template<class T>
constexpr T f(const T input) {
return std::abs(input);
}
int main() {
int i = -1;
int a = f(i);
return 0;
}
代码:
- 无论有没有模板,都可以使用 GCC 正常编译。
- 它在 Clang 中不起作用。
- 在 Visual Studio 中,它可以使用模板行进行编译,但如果没有模板,则编译失败。
最佳答案
对于常规函数,编译器可能会根据函数参数的类型知道内部代码是否可以在编译时进行评估。这就是为什么在 MSVC 和 clang 中调用 std::abs
时出现错误。 gcc 的行为基于其将 std::abs
实现为 constexpr
的决定,顺便说一句 a questionable decision .
对于模板函数,编译器无法知道内部代码是否可以在编译时求值,因为它可能基于模板参数的实际类型,调用不同的函数重载。虽然大多数编译器会决定不检查 std::abs
的所有可能重载是否不能是 constexpr
,从而让代码通过编译,但理论上编译器可能会检查(在非常情况下)可以检查的特定情况,就像这个),并且由于不允许用户通过添加新版本的 abs
来扩展 std
(允许扩展的列表std
已被规范关闭),可以看到该函数永远不可能是 constexpr
,从而生成编译错误。然而,在更一般的情况下,如果所有可能的情况都无法生成 constexpr 函数,则编译器无法检查模板函数,因为每次对模板的调用它只会看到内部调用的可用重载函数,并且当在其他地方调用模板时,内部调用可能还有其他可用的重载。
请注意,将 constexpr
函数设置为模板,以便对其进行编译,这并不是一个好方法。函数是否为 constexpr(即可以在编译时调用)的实际决定将基于实际调用,并且如果在所有情况下该函数都不能为 constexpr,则您试图以某种方式欺骗编译器,但最终主要是在欺骗你自己......
顺便说一句,在我检查 clang 10.1 和 trunk 版本时,我在模板版本上没有收到编译错误,此代码 compiles both with gcc and clang :
template<typename T>
constexpr T myabs(T t) {
return std::abs(t);
}
int main() {
int i = myabs(3);
}
虽然使用 gcc 进行编译(将 std::abs
实现为 constexpr
)并因 clang 失败:
int main() {
constexpr int i = myabs(3);
}
即使 constexpr
模板函数内的内部调用不依赖于模板参数and can never be a constant expression :
int myabs() {
return 42;
}
template<class T>
constexpr int f() {
// this is never a contexpr
// yet gcc and clang are ok with it
return myabs();
}
再次强调,这是允许的,因为对于不合格的 constexpr
模板函数不需要进行诊断:
[dcl.constexpr] 9.2.5/7 - The constexpr and consteval specifiers :
[...] If no specialization of the template would satisfy the requirements for a constexpr function when considered as a non-template function, the template is ill-formed, no diagnostic required.
关于c++ - std::abs 可以在 constexpr 函数中使用,但前提是它是模板化的。为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64220243/