问题描述
假设标准中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
,从而让代码通过编译,但是从理论上讲,编译器可以进行检查(在可以检查的非常特殊的情况下,例如一个),并且由于不允许用户通过添加新版本的std
来扩展abs
(规范关闭了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);
}
即使constexpr
模板函数内部的内部调用不依赖于模板参数,似乎 gcc 和 clang 都不会产生错误。 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:
[...]如果在将模板视为非模板函数时,如果模板的任何专业化都无法满足constexpr函数的要求,则模板格式错误,无需诊断。