MSVC和clang在if constexpr分支上的行为不同

16

给定这个辅助函数:

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): error C2668: 'fpclassify': 对重载函数的调用不明确

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

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

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

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

2> with

2> [

2> _Ty=int

2> ]

2>: note: 参见正在编译的函数模板实例化 'bool isnan(_Ty) noexcept'

2> with

2> [

2> Type=int,

2> _Ty=int

2> ]

显然编译器将 if constexpr (std::is_arithmetic<Type>::value) 也视为有效的备选项,并生成了上述错误。但是,在运行时,它正确地采用了bool路径(当我省略if constexpr (std::is_arithmetic<Type>::value)部分或使用强制转换if (std::isnan(static_cast<double>(value))))).

我该如何在Windows上使其正确编译?

2个回答

8

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,它将触发完全相同的错误:https://godbolt.org/z/u4e0HF。 - Daniel Langr
这是 OP 的原始代码的错误。如果它是一个 int 而不是 nan 呢?它不会返回任何东西。 - Hatted Rooster
@DanielLangr,缺少该重载可能是实现中的一个错误。 - Evg
1
@Evg 我的观点是,这个问题主要是由于MSVC实现中的一个错误引起的(整数类型缺少std::isnanstd::isinf的重载),而不是像你的答案所暗示的那样缺少了else - Daniel Langr
1
@DanielLangr,我的目的是要展示另一个观点:如果必须排除一个分支,你可以使用else if。在这种特定情况下,你可以重新设计代码以获得更好的解决方案。我完全同意这一点。 - Evg
显示剩余7条评论

7

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分支会被自动执行。为什么MSVC会将输入类型设置为int呢?这对我来说似乎是编译器的一个小陷阱。 - Mike Lischke
@MikeLischke 但这就是问题所在 - 因为你没有else,而且bool既是算术类型,也与bool相同(显然),所以两个if都是true。 - Steve
@Steve 如果有多个条件为真的if constexpr,这会成为一个问题吗? - Daniel Langr
好的,我明白了。由于clang在使用std :: isnanstd :: isinf时不会针对bool值产生警告或错误,因此很难完全检查这一点。因此,我无法确定clang在编译布尔分支中的返回时是否会进行优化。 - Mike Lischke
@MikeLischke 为什么会产生警告?bool是一种整型,因此它只使用整数参数的重载,这是正确的行为。 - Daniel Langr
@MikeLischke,这不是优化的问题。如果clang没有整数类型的重载,你会得到相同的错误。即使函数在运行时没有被调用,编译器也必须能够生成调用。 - Evg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接