C++ std::abs可用于constexpr函数,但前提是它';它是模板。为什么?

C++ std::abs可用于constexpr函数,但前提是它';它是模板。为什么?,c++,constexpr,function-templates,C++,Constexpr,Function Templates,标准中假定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; } 模板 常量表达式TF

标准中假定
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;
}
模板
常量表达式TF(常量输入){
返回标准::abs(输入);
}
int main(){
int i=-1;
int a=f(i);
返回0;
}
守则:

  • 无论是否使用模板,使用GCC都可以很好地编译
  • 它在叮当声中不起作用
  • 在VisualStudio中,它使用模板行进行编译,但在没有模板的情况下编译失败
对于常规函数编译器可能知道,根据函数参数的类型,是否可以在编译时对内部代码进行潜在评估。这就是为什么在MSVC和clang中调用
std::abs时出错的原因。gcc的行为基于其将
std::abs
实现为
constepr
的决策,顺便说一下

对于模板函数编译器无法知道是否可以在编译时计算内部代码,因为它可能基于模板参数的实际类型,并调用不同的函数重载。虽然大多数编译器会决定不检查
std::abs
的所有可能的重载是否都不能
constepr
,从而让代码通过编译,但理论上编译器可以检查(在可以检查的非常特定的情况下,如本例)由于不允许用户通过添加新版本的
abs
来扩展
std
(允许扩展到
std
的列表由规范关闭),因此可以看到函数永远不能是
constepr
,从而生成编译错误。但是,在更一般的情况下,如果所有可能的情况下都无法生成
constepr
函数,则编译器无法检查模板函数,因为每次调用模板函数时,它只能看到内部调用的可用重载,并且内部调用可能有其他可用重载,在别处调用模板时


请注意,将
constexpr
函数作为模板,只是为了编译它,并不是一个好方法。如果函数是
constexpr
(即可以在编译时调用),则实际决定将基于实际调用,如果在所有情况下函数都不能是
constexpr
,则您试图以某种方式欺骗编译器,但最终欺骗的主要是您自己


顺便说一下,在我对clang 10.1和trunk版本的检查中,我没有发现模板版本的编译错误,以下代码:

即使
constexpr
模板函数中的内部调用不依赖于模板参数,gcc和clang似乎都不会生成错误:

int-myabs(){
返回42;
}
模板
constexpr int f(){
//这从来不是一个上下文
//但gcc和clang对此没有意见
返回myabs();
}
同样,这是允许的,因为不符合
constexpr
模板功能不需要诊断:

:

[…]如果模板的专门化在被视为非模板函数时不能满足constexpr函数的要求,则模板的格式不正确,不需要诊断


不,它确实有效!甚至在调用时。我已经有一段时间不知情地使用了这个“把戏”。这两种情况都有投诉。这是否回答了您的问题?尝试在MSVC中将
constexpr
添加到您的
i
a
-它是否编译?如果不是,那么
f
就不是一个真正的
constexpr
函数。“我的意思是,如果它像那样编译,它仍然是constexpr”-不,它不是这个意思。声明为
constexpr
的模板“退回”为非constexpr如果实例化不是constexpr在第二种情况下,函数模板是格式错误的NDR。没有一组模板参数会产生实际的constexpr函数(直到
std::abs
正式成为constexpr)。我一直认为作为constexpr成功编译意味着它至少在理论上可以在编译时运行?这个假设是错误的吗?@Basti对于模板函数,编译器没有义务检查所有可能的重载,并确保它永远不会是constexpr,在大多数情况下,它实际上无法执行该检查,因为每次调用模板函数时,它只会看到可用的重载,而在其他地方调用模板时,内部调用可能会有其他可用的重载。很少有情况可以检查它,例如直接调用std::函数时,但编译器也不必检查。@StoryTeller UnslanderMonica会将它添加到答案中,谢谢。
template<typename T>
constexpr T myabs(T t) {
    return std::abs(t);
}

int main() {
    int i = myabs(3);
}
int main() {
    constexpr int i = myabs(3);
}
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();
}