假定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编译良好,无论是否使用模板
- 它在克兰不起作用
- 在VisualStudio中,它使用模板行进行编译,但在没有模板的情况下编译失败
对于正则函数,编译器可能会根据函数参数的类型知道是否可以在编译时评估内部代码。这就是为什么在MSVC和clang中调用std::abs
时出错的原因。gcc的行为是基于其将std::abs
实现为constexpr
的决定,顺便说一句,这是一个有问题的决定。
对于模板函数,编译器无法知道是否可以在编译时评估内部代码,因为它可能基于模板参数的实际类型,并调用不同的函数重载。虽然大多数编译器会决定不检查std::abs
的所有可能过载是否都不能是constexpr
,从而使代码通过编译,理论上,编译器可以进行检查(在可以进行检查的非常特殊的情况下,如本例(,并且由于不允许用户通过添加新版本的abs
来扩展std
(std
的允许扩展列表由规范关闭(,因此可能会发现该函数永远不会是constexpr
,从而产生编译错误。然而,在更常见的情况下,如果所有可能的情况都不能生成constexpr
函数,编译器就不能检查模板函数,因为每次调用模板函数时,编译器只看到内部调用的可用重载,而当在其他地方调用模板时,内部调用可能还有其他可用重载。
注意,将constexpr
函数作为模板,只是为了让它可以被编译,这不是一个好方法。如果函数是constexpr
(即可以在编译时调用(,实际的决定将基于实际的调用,如果在所有情况下函数都不能是constexpr
,那么你试图欺骗编译器,但最终主要是欺骗自己。。。
顺便说一句,在我对clang10.1和trunk版本的检查中,我在模板版本上没有得到编译错误,这段代码用gcc和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
模板函数内部的内部调用不依赖于模板参数,并且永远不可能是常量表达式:
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-constexpr和consteval说明符:
[…]如果在将模板视为非模板函数时,模板的特殊化不能满足constexpr函数的要求,则该模板格式错误,无需诊断。