首页 > 解决方案 > 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;
}

编码:

标签: c++constexprfunction-templates

解决方案


对于常规函数,编译器可能会根据函数参数的类型知道内部代码是否可以在编译时进行评估。这就是您在调用std::absMSVCclang收到错误的原因。gcc的行为是基于其实施的决定std::absconstexpr顺便说一句,这是一个有问题的决定

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


请注意,将constexpr函数设为模板以使其可以编译,这不是一个好方法。函数是否是constexpr(即可以在编译时调用)的实际决定将基于实际调用,并且如果在所有情况下该函数都不是constexpr你试图以某种方式欺骗编译器但最终主要是在欺骗你自己。 ..


顺便说一句,在我对clang 10.1 和 trunk 版本的检查中,我没有在模板版本上遇到编译错误,此代码同时使用 gcc 和 clang 编译

template<typename T>
constexpr T myabs(T t) {
    return std::abs(t);
}

int main() {
    int i = myabs(3);
}

虽然这使用 gcc 编译(实现std::absas 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 函数的要求,则模板格式错误,不需要诊断。


推荐阅读