Различное поведение 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 kits\10\include\10.0.10240.0\ucrt\math.h(403): error C2668: 'fpclassify': ambiguous call to overloaded function

2>c:\program files (x86)\windows kits\10\include\10.0.10240.0\ucrt\math.h(288): note: could be '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): note: while trying to match the argument list '(_Ty)'

2> with

2> [

2> _Ty=int

2> ]

2>: note: see reference to function template instantiation 'bool isnan(_Ty) noexcept' being compiled

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?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
16
0
566
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Для 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.

Daniel Langr 29.05.2019 10:43

Это вина исходного кода OP. Что, если это int, а не nan? Он ничего не возвращает.

Hatted Rooster 29.05.2019 10:45

@SombreroChicken Это немного странно, поскольку, согласно en.cppreference.com/w/cpp/numeric/math/isnan, должна быть перегрузка для целочисленных типов, которая должна вести себя так же, как перегрузка для double. Минимальный пример: godbolt.org/z/qcTfQs.

Daniel Langr 29.05.2019 10:48

@DanielLangr, тогда вам следует поставить еще один if constexpr, прежде чем пытаться вызывать isnan.

Evg 29.05.2019 10:49

@DanielLangr, отсутствие этой перегрузки, вероятно, является ошибкой в ​​реализации.

Evg 29.05.2019 10:52

@SombreroChicken, это не ошибка, это следствие сокращения до минимального примера. Исходный код в этом отношении был в полном порядке.

Evg 29.05.2019 10:57

@Evg Я хочу сказать, что проблема в первую очередь вызвана ошибкой в ​​реализации MSVC (отсутствует перегрузка std::isnan и std::isinf для целочисленных типов), а не отсутствием else, как предполагает ваш ответ.

Daniel Langr 29.05.2019 11:00

Вот и я сейчас тоже так думаю.

Mike Lischke 29.05.2019 11:02

@DanielLangr, это верно для этой проблемы особый. Но что, если бы у isnan не было перегрузки для целочисленного типа? Затем вы должны использовать else ifs, чтобы исключить эту ветвь. Поэтому я думаю, что этот ответ по-прежнему актуален для вопроса, потому что кто-то другой может найти этот вопрос, пытаясь решить аналогичную проблему, но не совсем ту же.

Evg 29.05.2019 11:08

@Evg Если вам нужно в ветке вызвать функцию, предназначенную только для аргументов с плавающей запятой, не лучше ли просто использовать if constexpr (std::is_floating_point_v<Type>)? Тогда не нужно else if.

Daniel Langr 29.05.2019 11:14

@Evg Если вы проверяете арифметический тип, а затем вызываете функцию, которая не действительна для всех арифметических типов, это неправильный дизайн. Это не имеет ничего общего с наличием/отсутствием else. Кстати, я не говорил, что ваш вопрос неактуален. Я думаю, что это.

Daniel Langr 29.05.2019 11:16

@DanielLangr, моя цель состояла в том, чтобы продемонстрировать еще один момент: вы можете использовать else if, чтобы исключить ветку, если вам нужно. В этом конкретном случае вы можете изменить код, чтобы получить еще лучшее решение. Я полностью согласен с этим.

Evg 29.05.2019 11:28

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

Это то, что я сделал в попытке решить проблему, но это вообще не должно быть необходимо, так как логическая ветвь все равно берется для логического ввода. Почему MSVC вообще делает тип ввода int? Это кажется мне компилятором.

Mike Lischke 29.05.2019 10:43

@MikeLischke, но в этом проблема - вызывались обе ветки, потому что у вас не было else, а bool является одновременно арифметическим типом и таким же, как bool (очевидно). Так что оба, если бы были правдой.

Steve 29.05.2019 10:52

@Steve И это проблема иметь несколько if constexpr с истинным оценочным условием?

Daniel Langr 29.05.2019 10:55

Да я вижу. Немного сложно проверить это полностью, так как clang не выдает предупреждение или ошибку для логических значений при использовании с std::isnan или std::isinf. Следовательно, я не могу точно сказать, оптимизирует ли clang это при компиляции возврата в ветке bool.

Mike Lischke 29.05.2019 10:57

@MikeLischke Почему должно выдаваться предупреждение? bool — целочисленный тип, поэтому он просто использует перегрузку для целочисленных аргументов, что является правильным поведением.

Daniel Langr 29.05.2019 11:02

@MikeLischke, дело не в оптимизации. Если бы у clang не было перегрузки для целочисленных типов, вы бы получили ту же ошибку. Даже если функция не вызывается во время выполнения, компилятор должен иметь возможность генерировать вызов.

Evg 29.05.2019 11:03

Другие вопросы по теме