Зачем использовать «b <a? a: b »вместо« a <b? b: a ”для реализации шаблона max?

154

Шаблоны C ++ - полное руководство, второе издание, представляет шаблон max :

template<typename T>
T max (T a, T b)
{
  // if b < a then yield a else yield b
  return  b < a ? a : b;
}

И это объясняет использование “b < a ? a : b”вместо “a < b ? b : a”:

Обратите внимание, что шаблон max () в соответствии с [StepanovNotes] намеренно возвращает «b <a? a: b »вместо« a <b? b: a ”, чтобы гарантировать, что функция ведет себя правильно, даже если два значения эквивалентны, но не равны.

Как понять " even if the two values are equivalent but not equal."? “a < b ? b : a”кажется, имеют тот же результат для меня.

Нань Сяо
источник
8
Видать неправильно ко мне ... Оба ответа «правильно», но если aи bявляются эквивалентными , то !(a < b) && !(b < a)верно, так a < bи b < aявляются ложными, так и в b < a ? a : b, bвозвращается, что не то , что вы хотите ... Вы хотите a < b ? b : a.
Холт
1
Часто можно различить эквивалент aи bс std::addressofэт. и др.
Caleth
14
Если вы делаете a = max(a, b);(неоднократно), вы не можете заменить aбез необходимости.
Бо Перссон
2
Кстати, этот шаблон должен принимать параметры по const-ссылкам и возвращать их по const-ссылкам, в противном случае вы делаете кучу бесполезных копий (и вы собираетесь переопределить aкопию a).
Холт
3
@Caleth: Канонический тип, который имеет как эквивалентность, так и равенство, является CaseInsensitiveString. Для этого типа ни a <A, ни A <a. Но не std::addressofимеет значения. На самом деле, для данного T max(T a, T b)мы уже знаем addressof(a) != addressof(b).
MSalters

Ответы:

150

std::max(a, b)действительно указано возвращать, aкогда два эквивалентны.

Степанов и другие считают это ошибкой, потому что это нарушает данное полезное свойство, aи bвы всегда можете их отсортировать {min(a, b), max(a, b)}; для этого вы хотите max(a, b)вернуть, bкогда аргументы эквивалентны.

TC
источник
48
С этой ссылке «Это трудно для меня , чтобы обвинить людей , которые делают так: в конце концов, они просто следуют C ++ стандартной спецификации макс . Написано мной Мне потребовалось несколько лет , чтобы увидеть , что я ошибся.» - Вот это да!
Джек Эйдли
23
ты не можешь просто сделать {min(a, b), max(b, a)}?
Капитан Мэн
12
@CaptainMan: Да, но это все еще менее очевидно. Я бы сказал, что имеет логический смысл, что max(a,b)будет возвращать if-and-only-if min(a,b)возвращает b, и наоборот, так что они противоположны друг другу, и (неупорядоченный) набор {min(a,b), max(a,b)}всегда равен {a,b}.
Джек Эйдли
5
@ jpmc26: Если кто-то, например, сортирует список событий по времени, не нужно заботиться о том, стабильна ли операция сортировки, чтобы заботиться о том, чтобы каждое событие появлялось ровно один раз во входных данных, а также появлялось ровно один раз в выходных данных. Некоторые операции (например, поиск и устранение дублированных событий) могут потребовать использования полного упорядочения, но во многих других случаях может быть допустимо перечислять одновременные события в произвольном порядке, но не дублировать или опускать их.
суперкат
2
@supercat Применять minи maxко всему, кроме метки времени (ключа сортировки) в этом сценарии, не имеет смысла. Сами события (объекты) не должны быть даже сопоставимы, если равенство не подразумевает взаимозаменяемость. Единственный способ {min(a, b), max(a, b)}имеет никакого смысла , так как механизм сортировки , если объекты являются взаимозаменяемыми.
jpmc26
62

Этот ответ объясняет, почему данный код является неправильным с точки зрения стандарта C ++, но это вне контекста.

См . Ответ @ TC для контекстного объяснения.


Стандарт определяет std::max(a, b)следующее [alg.min.max] (акцент мой):

template<class T> constexpr const T& max(const T& a, const T& b);

Требуется : Тип T является Менее сопоставимым (Таблица 18).

Возвращает : большее значение.

Замечания : Возвращает первый аргумент, когда аргументы эквивалентны.

Эквивалент здесь означает, что !(a < b) && !(b < a)это true [alg.sorting # 7] .

В частности, если aи bэквивалентны, то и так a < bи b < aесть false, поэтому значение справа :будет возвращено в условный оператор, поэтому aдолжно быть справа, так:

a < b ? b : a

... кажется правильным ответом. Это версия, используемая libstdc ++ и libc ++ .

Таким образом, информация в вашей цитате кажется неверной в соответствии с текущим стандартом, но контекст, в котором она определена, может отличаться.

нора
источник
4
Ссылка Godbolt, которая объясняет проблему (спасибо @songyuanyao за определение X).
Холт
1
@JackAidley Я отредактировал ответ, чтобы указать, что рассуждения нацелены на текущий стандарт.
Холт
@codekaizer Я на самом деле имел в виду «Если мы определим эквивалент (a, b) как! comp (a, b) &&! comp (b, a)» . Я изменил ссылку на лучшую цитату (3 строки ниже в стандарте ...).
Холт
1
Удивительно, что никто не упомянул с плавающей запятой, где a<bи b<aоба могут быть ложными, потому что они неупорядочены (один или оба NaN, поэтому ==тоже ложные) Это можно рассматривать как своего рода эквивалентность. слабо связанный: maxsd a, bинструкция x86 реализует a = max(b,a) = b < a ? a : b. ( Какова инструкция, которая дает минимальные и максимальные FP без ветвей на x86? ). Инструкция сохраняет исходный операнд (2-й) неупорядоченным, поэтому цикл по массиву даст вам NaN, если бы были какие-либо NaN. Но max_seen = max(max_seen, a[i])будут игнорировать NaNs.
Питер Кордес
21

Дело в том, какой из них должен быть возвращен, когда они эквивалентны; std::maxдолжен вернуть a(т.е. первый аргумент) для этого случая.

Если они эквивалентны, возвращается a.

Так a < b ? b : aдолжно быть использовано; с другой стороны, b < a ? a : b;вернется bнеправильно.

(Как сказал @Holt, цитата кажется противоположной.)

«эти два значения эквивалентны, но не равны» означает, что при сравнении они имеют одинаковое значение, но в некоторых других аспектах они могут быть разными объектами.

например

struct X { int a; int b; };
bool operator< (X lhs, X rhs) { return lhs.a < rhs.a; }
X x1 {0, 1};
X x2 {0, 2};
auto x3 = std::max(x1, x2); // it's guaranteed that an X which cantains {0, 1} is returned
songyuanyao
источник
1
Не могли бы вы уточнить, почему std::max(a, b)должен вернуться a, если aи bэквивалентны?
ク り ネ ロ ク
4
@ ネ ロ ク - Это просто произвольный выбор со стороны стандарта . Хотя это немного спорно , если это хороший один.
StoryTeller - Unslander Моника
Это только у меня так или это противоречит вопросу? Если aи bэквивалентны, то !(a < b) && !(b < a)верно, так a < bи b < aложно, так ...?
Холт
2
@ ネ ロ ク Полагаю, стандарт просто хочет, чтобы он определился; когда они эквивалентны, какой должен быть возвращен.
songyuanyao
1
спасибо, что освежил мою память о том, почему объект будет "эквивалентным", но не "равным".
Джефф Уокер,