Вопрос довольно понятен. Ниже приводится причина, по которой я думаю, что эти выражения могут привести к неопределенному поведению. Я хотел бы знать, правильны ли мои рассуждения или нет и почему.
Краткое чтение:
(IEEE 754) double
не является Cpp17LessThanComparable, поскольку <
не является строгим отношением слабого порядка из-за NaN
. Поэтому элементы std::min<double>
в std::min<double>
и std::max<double>
нарушаются.
Долго читал:
Все ссылки следуют за n4800. Спецификации std::min
и std::max
приведены в 24.7.8:
template<class T> constexpr const T& min(const T& a, const T& b);
template<class T> constexpr const T& max(const T& a, const T& b);
Требуется: [...] тип T должен быть Cpp17LessThanComparable (Таблица 24).
Таблица 24 определяет Cpp17LessThanComparable и говорит:
Требование:
<
строгое слабое отношение порядка (24.7)
Раздел 24.7/4 определяет строгий слабый порядок. В частности, для <
он гласит, что "если мы определим equiv(a, b)
как !(a < b) && !(b < a)
то equiv(a, b) && equiv(b, c)
подразумевает equiv(a, c)
".
Теперь, согласно IEEE 754 equiv(0.0, NaN) == true
, equiv(NaN, 1.0) == true
equiv(0.0, 1.0) == false
мы заключаем, что <
не является строгим слабым порядком. Таким образом, (IEEE 754) double
не является Cpp17LessThanComparable, что является нарушением условий Требований std::min
и std::max
.
Наконец, 15.5.4.11/1 говорит:
Нарушение любых предварительных условий, указанных в функции Требуется: элемент приводит к неопределенному поведению [...].
Обновление 1:
Суть вопроса не в том, чтобы утверждать, что std::min(0.0, 1.0)
не определен, и что-либо может произойти, когда программа оценивает это выражение. Возвращает 0.0
. Период. (Я никогда не сомневался в этом.)
Смысл в том, чтобы показать (возможный) дефект стандарта. В похвальном стремлении к точности Стандарт часто использует математическую терминологию, и слабый строгий порядок является лишь одним примером. В этих случаях математическая точность и рассуждение должны идти до конца.
Посмотрите, например, определение Википедии о строгом слабом порядке. Он содержит четыре маркера, и каждый из них начинается с "Для каждого x [...] в S...". Никто из них не говорит "Для некоторых значений x в S, которые имеют смысл для алгоритма" (Какой алгоритм?). Кроме того, спецификация std::min
ясна в том, что " T
должно быть Cpp17LessThanComparable", что влечет за собой то, что <
является строгим слабым порядком на T
Следовательно, T
играет роль множества S на странице Википедии, и четыре маркера должны сохраняться, когда значения T
рассматриваются полностью.
Очевидно, что NaN - совершенно разные звери от других двойных значений, но они все еще являются возможными значениями. Я не вижу ничего в Стандарте (который довольно большой, 1719 страниц, и, следовательно, этот вопрос и тег language-lawyer), который математически приводит к выводу, что std::min
подходит для удвоений при условии, что NaN не участвуют.
На самом деле, можно утверждать, что NaNs в порядке, и другие двойники являются проблемой! Действительно, напомним, что существует несколько возможных двойных значений NaN (2 ^ 52 - 1 из них, каждое из которых несет различную полезную нагрузку). Рассмотрим множество S, содержащее все эти значения и один "нормальный" дубль, скажем, 42.0. В символах S = {42.0, NaN_1,..., NaN_n}. Оказывается, что <
строгий слабый порядок на S (доказательство оставлено читателю). Был ли этот набор значений, который имел в виду Комитет C++ при указании std::min
например, "пожалуйста, не используйте никакие другие значения, иначе строгое слабое упорядочение нарушено и поведение std::min
не определено"? Могу поспорить, что это не так, но я бы предпочел прочитать это в Стандарте, чем размышлять, что означают "некоторые значения".
Обновление 2:
Сравните декларацию std::min
(выше) с clamp
24.7.9:
template<class T> constexpr const T& clamp(const T& v, const T& lo, const T& hi);
Требуется: значениеlo
не должно быть большеhi
. Для первой формы тип T должен быть Cpp17LessThanComparable (Таблица 24). [...]
[Примечание: если избегатьNaN
, T может быть типом с плавающей запятой. - конец примечания]
Здесь мы ясно видим что-то, что говорит: " std::clamp
хорошо с двойными числами при условии, что NaN не участвуют". Я искал предложение того же типа для std::min
.
Стоит обратить внимание на пункт [structure.requirements]/8, который Барри упомянул в своем посте. По-видимому, это было добавлено после C++ 17 от P0898R0):
Требуемые операции любой концепции, определенной в этом документе, не обязательно должны быть полными функциями; то есть некоторые аргументы требуемой операции могут привести к тому, что требуемая семантика не будет удовлетворена. [Пример: требуемый оператор
<
концепции StrictTotallyOrdered (17.5.4) не соответствует семантическим требованиям этой концепции при работе с NaN. - конец примера] Это не влияет на то, удовлетворяет ли тип концепции.
Это явная попытка решить проблему, которую я поднимаю здесь, но в контексте концепций (и как указал Барри, Cpp17LessThanComparable не является концепцией). Кроме того, ИМХО этому пункту также не хватает точности.