Навіщо використовувати “b <a? a: b »замість« a <b? b: a ”для реалізації шаблону max?


154

Шаблони C ++ - Повне керівництво, 2-е видання представляє максимальний шаблон:

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ет. ін.
Калет

14
Якщо ви робите це a = max(a, b);(неодноразово), можливо, ви не захочете aзайво замінювати .
Бо Персон

2
BTW цей шаблон повинен приймати параметри шляхом const-посилань і повертати їх шляхом const-reference, інакше ви робите купу непотрібних копій (і збираєтеся замінити aкопію a).
Холт

3
@Caleth: Канонічним типом, який має як еквівалентність, так і рівність, є CaseInsensitiveString. Для цього типу ні а <А, ні А <а. Але 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коли аргументи рівнозначні.


48
З цим посиланням «Це важко для мене , щоб звинуватити людей , які роблять так: в кінці кінців, вони просто йдуть C ++ стандартної специфікації макс . Написано мною Мені знадобилося кілька років , щоб побачити , що я помилився.» - Ого!
Джек Едлі

23
ти не можеш просто зробити {min(a, b), max(b, a)}?
Людина капітана

12
@CaptainMan: Так, але це все ще менш очевидно. Я заперечую, що має логічний сенс те max(a,b), що повернеться "if-and-only", якщо min(a,b)повертає b, і навпаки, так що вони є зворотними один для одного і (не упорядкованим) набором {min(a,b), max(a,b)}завжди дорівнює {a,b}.
Джек Едлі

5
@ jpmc26: Якщо ви, наприклад, сортуєте список подій за часом, не потрібно дбати про те, чи стабільна операція сортування, щоб піклуватися про те, щоб кожна подія, яка відображається точно один раз у вході, також відображалася рівно один раз у висновку. Окремі операції (наприклад, пошук та усунення дублюваних подій) можуть вимагати використання повного впорядкування, але у багатьох інших випадках може бути прийнятним перераховувати одночасні події у довільному порядку, але не дублювати чи опускати їх.
supercat

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 я фактично мав на увазі "Якщо ми визначимо equiv (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 min та max на x86? ). Інструкція зберігає вихідний операнд (другий) не упорядкованим, тому цикл над масивом дасть вам NaN, якщо були NaN. Але max_seen = max(max_seen, a[i])буде ігнорувати NaN.
Пітер Кордес


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

1
Чи не могли б ви уточнити , чому std::max(a, b)повинен повернутися a, якщо aі bеквівалентні?
眠 り ネ ロ ク

4
@ ネ ロ ク - Це просто довільний вибір з боку стандарту . Хоча це трохи дискусійно, якщо він хороший.
StoryTeller - Невідповідна Моніка

Це тільки я чи це суперечить питанню? Якщо aі bє рівнозначними, то !(a < b) && !(b < a)це правда, так a < bі b < aпомилково, так ...?
Холт

2
@ ネ ロ ク Я думаю, стандарт просто хоче, щоб він визначив; коли вони еквівалентні, який з них потрібно повернути.
songyuanyao

1
дякую за оновлення моєї пам’яті про те, чому об’єкт був би «еквівалентним», але не «рівним».
Джефф Уокер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.