std::abs 可以在 constexpr 函数中使用,但前提是它是模板化的。为什么?
std::abs can be used in constexpr function, but only if it's templated. Why?
据说 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 和主干版本时,我没有在模板版本上遇到编译错误,这段代码 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);
}
似乎 gcc 和 clang 都不会产生错误,即使 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();
}
同样,这是允许的,因为 non-conforming 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.
据说 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 和主干版本时,我没有在模板版本上遇到编译错误,这段代码 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);
}
似乎 gcc 和 clang 都不会产生错误,即使 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();
}
同样,这是允许的,因为 non-conforming 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.