Warum „b <a“ verwenden? a: b “statt„ a <b? b: a ”um die maximale Vorlage zu implementieren?

154

C ++ - Vorlagen - In der vollständigen Anleitung, 2. Ausgabe , wird die maximale Vorlage vorgestellt:

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

Und es erklärt die Verwendung “b < a ? a : b”anstelle von “a < b ? b : a”:

Beachten Sie, dass die Vorlage max () gemäß [StepanovNotes] absichtlich „b <a? a: b “statt„ a <b? b: a ”, um sicherzustellen, dass sich die Funktion auch dann korrekt verhält, wenn die beiden Werte gleich, aber nicht gleich sind.

Wie verstehe ich " even if the two values are equivalent but not equal."? “a < b ? b : a”scheint das gleiche Ergebnis für mich zu haben.

Nan Xiao
quelle
8
Sieht aus falsch zu mir ... Beide Antworten sind „richtig“, aber wenn aund bsind gleichwertig , so !(a < b) && !(b < a)wahr ist , so a < bund b < asind beide falsch, so in b < a ? a : b, bzurückgeführt wird, das ist nicht das, was Sie wollen ... Sie wollen a < b ? b : a.
Holt
1
Sie können oft zwischen äquivalent aund bmit std::addressofet unterscheiden. al.
Caleth
14
Wenn Sie dies a = max(a, b);(wiederholt) tun, möchten Sie möglicherweise nicht aunnötig ersetzen .
Bo Persson
2
Übrigens sollte diese Vorlage Parameter durch const-Verweise nehmen und als const-Referenz zurückgeben, andernfalls machen Sie eine Reihe nutzloser Kopien (und Sie werden sie amit einer Kopie von überschreiben a).
Holt
3
@Caleth: Der kanonische Typ, der sowohl Äquivalenz als auch Gleichheit aufweist, ist der CaseInsensitiveString. Für diesen Typ ist weder a <A noch A <a. Ist std::addressofaber irrelevant. Tatsächlich T max(T a, T b)wissen wir das bereits addressof(a) != addressof(b).
MSalters

Antworten:

150

std::max(a, b)wird in der Tat angegeben, um zurückzukehren, awenn die beiden gleichwertig sind.

Dies wird von Stepanov und anderen als Fehler angesehen, da es die angegebene nützliche Eigenschaft verletzt aund bSie sie jederzeit sortieren können {min(a, b), max(a, b)}. Dafür möchten Sie max(a, b)zurückkehren, bwenn die Argumente gleichwertig sind.

TC
quelle
48
Von diesem Link "Es fällt mir schwer, die Leute zu beschuldigen, die dies tun: Schließlich folgen sie einfach der von mir geschriebenen C ++ - Standardspezifikation von max . Es hat mehrere Jahre gedauert , bis ich mich geirrt habe." - Beeindruckend!
Jack Aidley
23
kann nicht einfach tun Sie {min(a, b), max(b, a)}?
Captain Man
12
@ CaptainMan: Ja, aber es ist immer noch weniger offensichtlich. Ich würde argumentieren, dass es logisch sinnvoll ist, max(a,b)a genau dann zurückzugeben, wenn min(a,b)b zurückgegeben wird, und umgekehrt, so dass sie umgekehrt sind und die (ungeordnete) Menge {min(a,b), max(a,b)}immer gleich ist {a,b}.
Jack Aidley
5
@ jpmc26: Wenn man zB eine Liste von Ereignissen nach Zeit sortiert, muss man sich nicht darum kümmern, ob der Sortiervorgang stabil ist, um sich darum zu kümmern, dass jedes Ereignis, das genau einmal in der Eingabe erscheint, ebenfalls genau einmal in der Ausgabe erscheint. Bestimmte Vorgänge (wie das Auffinden und Entfernen doppelter Ereignisse) erfordern möglicherweise die Verwendung einer vollständigen Reihenfolge. In vielen anderen Fällen kann es jedoch akzeptabel sein, gleichzeitige Ereignisse in beliebiger Reihenfolge aufzulisten, sie jedoch nicht zu duplizieren oder wegzulassen.
Supercat
2
@supercat Das Anwenden minund maxauf alles andere als den Zeitstempel (den Sortierschlüssel) in diesem Szenario macht keinen Sinn. Die Ereignisse (die Objekte) selbst sollten nicht einmal vergleichbar sein, wenn Gleichheit keine Austauschbarkeit impliziert. Der einzige Weg , {min(a, b), max(a, b)}macht jeden Sinn als eine Art Mechanismus ist , wenn die Objekte untereinander austauschbar sind.
jpmc26
62

Diese Antwort erklärt, warum der angegebene Code aus Sicht des C ++ - Standards falsch ist, aber nicht im Zusammenhang steht.

Siehe @ TC Antwort für eine kontextuelle Erklärung.


Der Standard definiert std::max(a, b)wie folgt [alg.min.max] (Schwerpunkt liegt bei mir):

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

Erforderlich : Typ T ist LessThanComparable (Tabelle 18).

Rückgabe : Der größere Wert.

Anmerkungen : Gibt das erste Argument zurück, wenn die Argumente äquivalent sind.

Equivalent hier bedeutet , dass !(a < b) && !(b < a)sich true [alg.sorting # 7] .

Insbesondere wenn aund gleich bsind, beide a < bund b < asind false, so wird der Wert auf der rechten Seite :im bedingten Operator zurückgegeben, amuss also auf der rechten Seite sein, also:

a < b ? b : a

... scheint die richtige Antwort zu sein. Dies ist die von libstdc ++ und libc ++ verwendete Version .

Die Informationen in Ihrem Angebot scheinen nach dem aktuellen Standard falsch zu sein, aber der Kontext, in dem sie definiert sind, kann unterschiedlich sein.

Holt
quelle
4
Godbolt-Link , der das Problem erklärt (danke @songyuanyao für die Definition von X).
Holt
1
@ JackAidley Ich habe die Antwort bearbeitet, um anzugeben, dass die Argumentation auf den aktuellen Standard abzielt.
Holt
@codekaizer Ich bezog mich tatsächlich auf "Wenn wir Äquiv. (a, b) als! comp (a, b) &&! comp (b, a) definieren" . Ich habe den Link zu einem besseren Zitat geändert (3 Zeilen unten im Standard ...).
Holt
1
Überrascht hat niemand Gleitkomma erwähnt, wo a<bund b<akönnen beide falsch sein, weil sie ungeordnet sind (ein oder beide NaN, also auch ==falsch). Das könnte als eine Art Äquivalenz angesehen werden. lose verwandt: x86- maxsd a, bAnweisung implementiert a = max(b,a) = b < a ? a : b. ( Was ist die Anweisung, die verzweigungslose FP min und max auf x86 gibt? ). Der Befehl hält den Quelloperanden (den zweiten) ungeordnet, sodass eine Schleife über ein Array NaN ergibt, wenn NaNs vorhanden sind. Aber max_seen = max(max_seen, a[i])wird NaNs ignorieren.
Peter Cordes
Siehe auch Stepanovs Hinweise zur Programmierung
Shafik Yaghmour
21

Der Punkt ist, welcher zurückgegeben werden sollte, wenn sie gleichwertig sind; std::maxmuss afür diesen Fall zurückgeben (dh das erste Argument).

Wenn sie gleichwertig sind, wird zurückgegeben a.

Also a < b ? b : asollte verwendet werden; auf der anderen Seite b < a ? a : b;wird bfalsch zurückgegeben.

(Wie @Holt sagte, scheint das Zitat entgegengesetzt zu sein.)

"Die beiden Werte sind äquivalent, aber nicht gleich" bedeutet, dass sie beim Vergleich den gleichen Wert haben, aber in einigen anderen Aspekten unterschiedliche Objekte sein können.

z.B

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
quelle
1
Könnten Sie bitte erläutern, warum Sie std::max(a, b)zurückkehren müssen a, wenn aund bgleichwertig sind?
り ネ ロ ク
4
@ ネ ロ ク - Es ist nur eine willkürliche Wahl seitens des Standards . Obwohl es ein bisschen umstritten ist, wenn es gut ist.
Geschichtenerzähler - Unslander Monica
Ist es nur ich oder steht dies im Widerspruch zur Frage? Wenn aund bgleichwertig sind, dann !(a < b) && !(b < a)ist wahr, also a < bund b < asind falsch, also ...?
Holt
2
@ ネ ロ ク Ich nehme an, der Standard möchte ihn nur bestimmen lassen; wenn sie gleichwertig sind, welche zurückgegeben werden sollen.
Songyuanyao
1
Vielen Dank, dass Sie mein Gedächtnis darüber aufgefrischt haben, warum ein Objekt "gleichwertig", aber nicht "gleich" ist.
Jeff Walker