【问题标题】:Different behavior of MSVC and clang for if constexpr branches如果 constexpr 分支的 MSVC 和 clang 的不同行为
【发布时间】:2019-10-14 19:14:17
【问题描述】:

给定这个辅助函数:

template<typename Type>
std::string toString(Type const& value, bool encloseInQuotes = false) {
  if constexpr (std::is_same<bool, Type>::value) {
    auto s = value ? "true" : "false";
    return encloseInQuotes ? "\""s + s + "\"" : s;
  }

  if constexpr (std::is_arithmetic<Type>::value) {
    if (std::isnan(value)) {
      return encloseInQuotes ? "\"NaN\"" : "NaN";
    }
  }

  return "";
}

它应该将基本类型(和字符串)转换为字符串表达式,但在使用 MSVC 时会出现编译错误:

int main() {
  std::string temp = toString(true);
  return 0;
}

使用 clang 编译没有任何问题,但是使用 MSVC 我得到了这个:

2>c:\program files (x86)\windows kits\10\include\10.0.10240.0\ucrt\math.h(403): 错误 C2668: 'fpclassify': 对重载函数的模糊调用

2>c:\program files (x86)\windows kits\10\include\10.0.10240.0\ucrt\math.h(288):注意:可能是'int fpclassify(long double) noexcept'

2>c:\program files (x86)\windows kits\10\include\10.0.10240.0\ucrt\math.h(283): note: or 'int fpclassify(double) noexcept'

2>c:\program files (x86)\windows kits\10\include\10.0.10240.0\ucrt\math.h(278): note: or 'int fpclassify(float) noexcept'

2>c:\program files (x86)\windows kits\10\include\10.0.10240.0\ucrt\math.h(403):注意:在尝试匹配参数列表时'(_Ty)'

2> 与

2> [

2> _Ty=int

2>]

2>:注意:请参阅正在编译的函数模板实例化 'bool isnan(_Ty) noexcept' 的参考

2> 与

2> [

2> 类型=int,

2> _Ty=int

2>]

显然,编译器也将if constexpr (std::is_arithmetic&lt;Type&gt;::value) 测试视为有效的替代方案,并生成上述错误。但是,在运行时,它正确地采用了 bool 的路径(当我省略 if constexpr (std::is_arithmetic&lt;Type&gt;::value) 部分或使用演员 if (std::isnan(static_cast&lt;double&gt;(value))) 时)。

如何在 Windows 上也能正确编译?

【问题讨论】:

    标签: c++


    【解决方案1】:

    对于bool,至少有两个类型特征返回true

    std::is_same<bool, Type>::value
    std::is_arithmetic<Type>::value
    

    然后您拨打电话std::isnan(true)。使用else if:

    if constexpr (std::is_same<bool, Type>::value) {
        auto s = value ? "true" : "false";
        return encloseInQuotes ? "\""s + s + "\"" : s;
    }
    else if constexpr (std::is_arithmetic<Type>::value) {
        if (std::isnan(value)) {
            return encloseInQuotes ? "\"NaN\"" : "NaN";
        }
        ...
    }
    else
        return "";
    

    【讨论】:

    • 此解决方案不起作用,例如,如果Type 被推断为int。它会触发同样的错误:godbolt.org/z/u4e0HF.
    • 那是 OPs 原始代码的错。如果它是int 而不是nan 怎么办?它不返回任何东西。
    • @SombreroChicken 这有点奇怪,因为根据en.cppreference.com/w/cpp/numeric/math/isnan,整数类型应该有一个重载,它的行为应该与double 的重载相同。最小示例:godbolt.org/z/qcTfQs.
    • @Evg 我的意思是,这个问题主要是由 MSVC 实现中的一个错误引起的(对于整数类型缺少 std::isnanstd::isinf 的重载),而不是缺少 else,正如你的回答所暗示的那样。
    • @DanielLangr,我的目的是证明另一点:如果需要,您可以使用 else if 排除分支。在这种特殊情况下,您可以重新设计代码以获得更好的解决方案。我完全同意。
    【解决方案2】:

    std::isnanstd::isinf 在 MSVC 中看似内部调用 fpclassify。该函数对浮点类型进行了重载,并且您传递了bool 类型的参数,因此调用不明确

    为避免这种情况,您可以将参数转换为例如double

    if constexpr (std::is_arithmetic<Type>::value) {
      if (std::isinf((double)value)) {
        return encloseInQuotes ? "\"INF\"" : "INF";
      }
    
      if (std::isnan((double)value)) {
        return encloseInQuotes ? "\"NaN\"" : "NaN";
      }
    

    现场演示:https://godbolt.org/z/W7Z3r3


    更新

    这似乎是 MSVC 实现中的一个错误,因为根据 cppreference,应该有一个与 double 重载行为相同的整数参数的重载。最小的例子:

    auto b = std::isnan(1);
    

    现场演示:https://godbolt.org/z/qcTfQs

    【讨论】:

    • 这就是我试图解决问题的方法,但这根本没有必要,因为 bool 分支无论如何都会被用于 bool 输入。为什么 MSVC 甚至将输入类型设为 int?这对我来说似乎有点编译器问题。
    • @MikeLischke 但这就是问题所在 - 两个分支都被调用了,因为你没有 else,并且 bool 既是算术类型,又与 bool 相同(显然)。所以两个如果都是真的。
    • @Steve 多个if constexpr 的评估条件为真有问题吗?
    • 好的,我明白了。完全检查这一点有点困难,因为当与std::isnanstd::isinf 一起使用时,clang 不会对 bool 值产生警告或错误。因此,我不能确定当它在 bool 分支中编译返回时,clang 是否优化了这一点。
    • @MikeLischke 为什么要产生警告? bool 是一个整数类型,因此它只是对整数参数使用重载,这是正确的行为。
    猜你喜欢
    • 2018-04-26
    • 2021-03-09
    • 2022-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-05
    • 1970-01-01
    相关资源
    最近更新 更多