std::abs 可以在 constexpr 函数中使用,但前提是它是模板化的。为什么?



假定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中,它使用模板行进行编译,但在没有模板的情况下编译失败

对于正则函数,编译器可能会根据函数参数的类型知道是否可以在编译时评估内部代码。这就是为什么在MSVCclang中调用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);
}

似乎gccclang都不会生成错误,即使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函数的要求,则该模板格式错误,无需诊断。

相关内容

最新更新