Различное поведение MSVC и clang для веток constexpr

Учитывая эту вспомогательную функцию:

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 windows\10\include\10.0.10240.0\ucrt\math.h(403): ошибка C2668: 'fpclassify': неоднозначный вызов перегруженной функции

2> c:\program files (x86)\windows windows\10\include\10.0.10240.0\ucrt\math.h(288): примечание: может быть 'int fpclassify (long double) noexcept'

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

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

2> c:\program files (x86)\windows windows\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<Type>::value) как допустимую альтернативу и генерирует указанную ошибку. Однако во время выполнения он правильно принимает путь для bool (когда я if constexpr (std::is_arithmetic<Type>::value) или использую приведение if (std::isnan(static_cast<double>(value)))).

Как я могу сделать эту компиляцию правильно и в Windows?

Ответ 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 "";

Ответ 2

std::isnan и std::isinf видимому, внутренне вызывают fpclassify в MSVC. Эта функция перегружена для типов с плавающей точкой, и вы передаете аргумент типа 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