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;
}

代码:

对于常规函数,编译器可能会根据函数参数的类型知道,如果内部代码可能是在编译时评估。这就是为什么在 MSVCclang 中调用 std::abs 会出错的原因。 gcc 的行为是基于其将 std::abs 实现为 constexpr 的决定,顺便说一下 a questionable decision.

对于模板函数,编译器无法知道内部代码是否可以在编译时求值,因为它可能基于模板参数的实际类型,调用不同的函数重载。虽然大多数编译器会决定不检查是否所有可能的 std::abs 重载都不能是 constexpr,从而让代码通过编译,但理论上编译器可能会检查(在非常特殊的情况下可以检查,像这样一个)并且由于不允许用户通过添加 abs 的新版本来扩展 stdstd 的允许扩展列表已被规范关闭)所以可以看到该函数永远不能 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);
}

似乎 gccclang 都不会产生错误,即使 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.