Das Treffen in Köln ist vorbei, der C ++ 20-Standard wurde auf ein mehr oder weniger fertiges Aussehen reduziert (zumindest bis zum Erscheinen spezieller Hinweise), und ich möchte über eine der kommenden Innovationen sprechen. Dies ist ein Mechanismus, der normalerweise als
Operator <=> bezeichnet wird (der Standard definiert ihn als "Drei-Wege-Vergleichsoperator", hat aber den informellen Spitznamen "Raumschiff"), aber ich glaube, dass sein Anwendungsbereich viel breiter ist.
Wir werden nicht nur einen neuen Operator haben - die Semantik der Vergleiche wird sich auf der Ebene der Sprache selbst erheblich ändern.
Denken Sie an diese Tabelle, auch wenn Sie aus diesem Artikel nichts anderes herausholen können:
Jetzt haben wir einen neuen Operator,
<=> , aber was noch wichtiger ist, die Operatoren sind jetzt systematisiert. Es gibt grundlegende Operatoren und abgeleitete Operatoren - jede Gruppe hat ihre eigenen Fähigkeiten.
Wir werden in der Einleitung kurz auf diese Funktionen eingehen und in den folgenden Abschnitten näher darauf eingehen.
Grundlegende Operatoren können
invertiert werden (d. H. Mit der umgekehrten Reihenfolge der Parameter umgeschrieben werden). Abgeleitete Anweisungen können über die entsprechende Basisanweisung
umgeschrieben werden . Weder konvertierte noch umgeschriebene Kandidaten generieren neue Funktionen. Sie werden lediglich auf Quellcodeebene ersetzt und aus einer
erweiterten Gruppe von Kandidaten ausgewählt . Beispielsweise kann der Ausdruck
a <9 jetzt als
a.operator <=> (9) <0 und der Ausdruck
10! = B als
! Operator == (b, 10) ausgewertet werden. Dies bedeutet, dass auf einen oder zwei Operatoren verzichtet werden kann, bei denen zur Erzielung des gleichen Verhaltens jetzt 2, 4, 6 oder sogar 12 Operatoren manuell geschrieben werden müssen.
Im Folgenden wird
ein kurzer Überblick über die Regeln zusammen mit einer Tabelle aller möglichen Transformationen gegeben.
Sowohl Basisoperatoren als auch abgeleitete Operatoren können als
Standard definiert werden. Bei Basisoperatoren bedeutet dies, dass der Operator auf jedes Mitglied in der Deklarationsreihenfolge angewendet wird. Bei abgeleiteten Operatoren werden diese umgeschriebenen Kandidaten verwendet.
Es sollte beachtet werden, dass es keine solche Transformation gibt, bei der ein Operator einer Art (d. H. Gleichheit oder Ordnung) in Form eines Operators einer anderen Art ausgedrückt werden könnte. Mit anderen Worten, die Spalten in unserer Tabelle sind in keiner Weise voneinander abhängig. Der Ausdruck
a == b wird niemals implizit als
Operator <=> (a, b) == 0 ausgewertet (aber natürlich hindert Sie nichts daran, Ihren
Operator == mit dem
Operator <=> zu definieren, wenn Sie möchten).
Betrachten Sie ein kleines Beispiel, in dem wir zeigen, wie der Code vor und nach dem Anwenden der neuen Funktionalität aussieht. Wir werden einen String-Typ schreiben, bei dem nicht zwischen Groß- und Kleinschreibung unterschieden wird,
CIString , dessen Objekte sowohl miteinander als auch mit
char const * verglichen werden können.
In C ++ 17 müssen wir für unsere Aufgabe 18 Vergleichsfunktionen schreiben:
class CIString { string s; public: friend bool operator==(const CIString& a, const CIString& b) { return assize() == bssize() && ci_compare(asc_str(), bsc_str()) == 0; } friend bool operator< (const CIString& a, const CIString& b) { return ci_compare(asc_str(), bsc_str()) < 0; } friend bool operator!=(const CIString& a, const CIString& b) { return !(a == b); } friend bool operator> (const CIString& a, const CIString& b) { return b < a; } friend bool operator>=(const CIString& a, const CIString& b) { return !(a < b); } friend bool operator<=(const CIString& a, const CIString& b) { return !(b < a); } friend bool operator==(const CIString& a, const char* b) { return ci_compare(asc_str(), b) == 0; } friend bool operator< (const CIString& a, const char* b) { return ci_compare(asc_str(), b) < 0; } friend bool operator!=(const CIString& a, const char* b) { return !(a == b); } friend bool operator> (const CIString& a, const char* b) { return b < a; } friend bool operator>=(const CIString& a, const char* b) { return !(a < b); } friend bool operator<=(const CIString& a, const char* b) { return !(b < a); } friend bool operator==(const char* a, const CIString& b) { return ci_compare(a, bsc_str()) == 0; } friend bool operator< (const char* a, const CIString& b) { return ci_compare(a, bsc_str()) < 0; } friend bool operator!=(const char* a, const CIString& b) { return !(a == b); } friend bool operator> (const char* a, const CIString& b) { return b < a; } friend bool operator>=(const char* a, const CIString& b) { return !(a < b); } friend bool operator<=(const char* a, const CIString& b) { return !(b < a); } };
In C ++ 20 können Sie nur 4 Funktionen ausführen:
class CIString { string s; public: bool operator==(const CIString& b) const { return s.size() == bssize() && ci_compare(s.c_str(), bsc_str()) == 0; } std::weak_ordering operator<=>(const CIString& b) const { return ci_compare(s.c_str(), bsc_str()) <=> 0; } bool operator==(char const* b) const { return ci_compare(s.c_str(), b) == 0; } std::weak_ordering operator<=>(const char* b) const { return ci_compare(s.c_str(), b) <=> 0; } };
Ich werde Ihnen genauer sagen, was das alles bedeutet, aber lassen Sie uns zunächst ein wenig zurückgehen und uns daran erinnern, wie Vergleiche mit dem C ++ 20-Standard funktioniert haben.
Vergleiche in Standards von C ++ 98 bis C ++ 17
Die Vergleichsoperationen haben sich seit der Erstellung der Sprache nicht wesentlich geändert. Wir hatten sechs Operatoren:
==,! = ,
< ,
> ,
<= Und
> = . Der Standard definiert jeden von ihnen für eingebaute Typen, aber im Allgemeinen befolgen sie dieselben Regeln. Bei der Auswertung eines
a @ b- Ausdrucks (wobei
@ einer von sechs Vergleichsoperatoren ist) sucht der Compiler nach Elementfunktionen, freien Funktionen und integrierten Kandidaten mit dem Namen
operator @ , die in der angegebenen Reihenfolge mit Typ
A oder
B aufgerufen werden können. Aus ihnen wird der am besten geeignete Kandidat ausgewählt. Das ist alles. Tatsächlich arbeiteten
alle Operatoren auf die gleiche Weise: Die Operation
<unterschied sich nicht von
<< .
Solch ein einfaches Regelwerk ist leicht zu erlernen. Alle Betreiber sind absolut unabhängig und gleichwertig. Es spielt keine Rolle, was wir Menschen über die grundlegende Beziehung zwischen
== und
! = Operationen wissen. In Bezug auf die Sprache ist dies ein und dasselbe. Wir verwenden Redewendungen. Zum Beispiel definieren wir den Operator
! = Through
== :
bool operator==(A const&, A const&); bool operator!=(A const& lhs, A const& rhs) { return !(lhs == rhs); }
In ähnlicher Weise definieren wir durch den Operator
< alle anderen Beziehungsoperatoren. Wir verwenden diese Redewendungen, weil wir trotz der Regeln der Sprache nicht alle sechs Operatoren als gleichwertig betrachten. Wir akzeptieren, dass zwei von ihnen grundlegend sind (
== und
< ), und durch sie werden alle anderen bereits ausgedrückt.
Tatsächlich basiert die Standardvorlagenbibliothek vollständig auf diesen beiden Operatoren, und die große Anzahl von Typen im ausgenutzten Code enthält Definitionen von nur einem oder beiden.
Der Operator
< ist jedoch aus zwei Gründen für die Basiserolle nicht sehr geeignet.
Erstens kann nicht garantiert werden, dass andere Beziehungsoperatoren sich dadurch ausdrücken. Ja,
a> b bedeutet genau dasselbe wie
b <a , aber es ist nicht wahr, dass
a <= b genau dasselbe bedeutet wie
! (B <a) . Die letzten beiden Ausdrücke sind äquivalent, wenn es eine Eigenschaft der Trichotomie gibt, bei der für zwei beliebige Werte nur eine der drei Aussagen wahr ist:
a <b ,
a == b oder
a> b . Bei Vorliegen einer Trichotomie bedeutet der Ausdruck
a <= b , dass es sich entweder um den ersten oder den zweiten Fall handelt ... und dies entspricht der Aussage, dass es sich nicht um den dritten Fall handelt. Daher
(a <= b) ==! (A> b) ==! (B <a) .
Aber was ist, wenn die Haltung nicht die Eigenschaft der Trichotomie besitzt? Dies ist charakteristisch für partielle Ordnungsbeziehungen. Ein klassisches Beispiel sind Gleitkommazahlen, für die eine der Operationen
1.f <NaN ,
1.f == NaN und
1.f> NaN false ergibt. Daher
lügt auch
1.f <= NaN , aber gleichzeitig
! (NaN <1.f) ist
wahr .
Die einzige Möglichkeit, den Operator
<= allgemein über die Basisoperatoren zu implementieren, besteht darin, beide Operationen als
(a == b) || zu zeichnen
(a <b) , was ein großer Rückschritt ist, wenn wir uns
noch mit linearer Ordnung befassen müssen, da dann nicht eine Funktion aufgerufen wird, sondern zwei (zum Beispiel der Ausdruck
„abc..xyz9“ <= „abc ..xyz1 " muss umgeschrieben werden als
(" abc..xyz9 "==" abc..xyz1 ") || (" abc..xyz9 "<" abc..xyz1 ") und zweimal, um die gesamte Zeile zu vergleichen).
Zweitens ist der Operator
< aufgrund der Besonderheiten seiner Verwendung in lexikografischen Vergleichen für die Grundrolle nicht sehr geeignet. Programmierer machen oft diesen Fehler:
struct A { T t; U u; bool operator==(A const& rhs) const { return t == rhs.t && u == rhs.u; } bool operator< (A const& rhs) const { return t < rhs.t && u < rhs.u; } };
Um den Operator == für eine Sammlung von Elementen zu definieren, reicht es aus,
== einmal auf jedes Mitglied anzuwenden. Dies funktioniert jedoch nicht mit dem Operator
< . Unter dem Gesichtspunkt dieser Implementierung werden die Mengen
A {1, 2} und
A {2, 1} als äquivalent angesehen (da keine von ihnen kleiner als die andere ist). Um dies zu beheben, wenden Sie den Operator
< zweimal auf jedes Mitglied an, mit Ausnahme des letzten:
bool operator< (A const& rhs) const { if (t < rhs.t) return true; if (rhs.t < t) return false; return u < rhs.u; }
Schließlich, um den korrekten Betrieb von Vergleichen heterogener Objekte zu gewährleisten - d.h. Um sicherzustellen, dass die Ausdrücke
a == 10 und
10 == a dasselbe bedeuten, empfehlen sie normalerweise, Vergleiche als freie Funktionen zu schreiben. Tatsächlich ist dies im Allgemeinen die einzige Möglichkeit, solche Vergleiche durchzuführen. Dies ist unpraktisch, da Sie erstens die Einhaltung dieser Empfehlung überwachen müssen und zweitens normalerweise solche Funktionen als versteckte Freunde deklarieren müssen, um eine bequemere Implementierung zu ermöglichen (d. H. Innerhalb des Klassenkörpers).
Beachten Sie, dass beim Vergleichen von Objekten unterschiedlichen Typs nicht immer der
Operator == (X, int) geschrieben werden muss . Sie können auch Fälle bedeuten, in denen
int implizit in
X umgewandelt werden kann .
Fassen wir die Regeln zum C ++ 20-Standard zusammen:
- Alle Anweisungen werden gleich behandelt.
- Wir verwenden Redewendungen, um die Implementierung zu erleichtern. Die Operatoren == und < nehmen wir für die Grundsprachen und drücken die verbleibenden Beziehungsoperatoren durch sie aus.
- Das ist nur der Operator <ist für die Rolle der Basis nicht sehr geeignet.
- Es ist wichtig (und empfohlen), Vergleiche heterogener Objekte als freie Funktionen zu schreiben.
Neuer grundlegender Bestelloperator: <=>
Die bedeutendste und auffälligste Änderung in der Arbeit von Vergleichen in C ++ 20 ist die Hinzufügung eines neuen Operators -
Operator <=> , eines Drei-Wege-Vergleichsoperators.
Drei-Wege-Vergleiche sind bereits mit den Funktionen
memcmp /
strcmp in C und
basic_string :: compare () in C ++ bekannt. Sie alle geben einen Wert vom Typ
int zurück , der durch eine beliebige positive Zahl dargestellt wird, wenn das erste Argument größer als das zweite ist,
0, wenn sie gleich sind, und ansonsten durch eine beliebige negative Zahl.
Der Operator "Raumschiff" gibt keinen
int- Wert zurück, sondern ein Objekt, das zu einer der Vergleichskategorien gehört, deren Wert die Art der Beziehung zwischen den verglichenen Objekten widerspiegelt. Es gibt drei Hauptkategorien:
- strong_ordering : Eine lineare Ordnungsbeziehung, in der Gleichheit die Austauschbarkeit von Elementen impliziert (d. h. (a <=> b) == strong_ordering :: gleich impliziert, dass f (a) == f (b) für alle geeigneten Funktionen f gilt Der Begriff „geeignete Funktion“ wird absichtlich nicht klar definiert, enthält jedoch keine Funktionen, die die Adressen ihrer Argumente oder die Kapazität () des Vektors usw. zurückgeben. Wir interessieren uns nur für die „wesentlichen“ Eigenschaften, die ebenfalls sehr vage, aber bedingt möglich sind Nehmen wir an, wir sprechen über den Wert des Typs. Der Wert des Vektors ist darin enthalten m Elemente, aber nicht seine Adresse usw.). Diese Kategorie enthält die folgenden Werte: strong_ordering :: größer , strong_ordering :: gleich und strong_ordering :: kleiner .
- schwache_ordnung : eine lineare Ordnungsbeziehung, in der Gleichheit nur eine bestimmte Äquivalenzklasse definiert. Ein klassisches Beispiel ist der Vergleich von Zeichenfolgen ohne Berücksichtigung der Groß- und Kleinschreibung, wenn zwei Objekte schwach_bestellend :: äquivalent sein können , aber nicht genau gleich sind (dies erklärt das Ersetzen des Wortes gleich durch äquivalent im Wertnamen ).
- Teilbestellung: Teilordnungsbeziehung. In dieser Kategorie wird ein weiterer Wert zu den Werten größer , äquivalent und kleiner hinzugefügt (wie bei schwach_bestellung ) - ungeordnet ("ungeordnet"). Es kann verwendet werden, um Teilordnungsbeziehungen in einem Typsystem auszudrücken : 1.f <=> NaN gibt den Wert partielle_bestellung :: ungeordnet an .
Sie arbeiten hauptsächlich mit der Kategorie
strong_ordering . Dies ist auch die optimale Kategorie für die standardmäßige Verwendung. Beispiel:
2 <=> 4 gibt
strong_ordering :: less und
3 <=> -1 gibt strong_ordering :: größer zurück .
Kategorien höherer Ordnung können implizit auf Kategorien schwächerer Ordnung reduziert werden (d. H.
Starke Ordnung ist auf schwache Ordnung reduzierbar). In diesem Fall bleibt der aktuelle Beziehungstyp erhalten (d. H.
Starke Ordnung :: gleich wird zu
schwacher Ordnung :: äquivalent ).
Die Werte der Vergleichskategorien können mit einem von sechs Vergleichsoperatoren mit Literal
0 (nicht mit
int und nicht mit
int gleich
0 , sondern einfach mit Literal
0 ) verglichen werden:
strong_ordering::less < 0
Dank eines Vergleichs mit dem Literal
0 können wir die Beziehungsoperatoren implementieren:
a @ b entspricht
(a <=> b) @ 0 für jeden dieser Operatoren.
Zum Beispiel kann
2 <4 als
(2 <=> 4) <0 berechnet werden, was zu
strong_ordering :: less <0 führt und den Wert
true ergibt.
Der Operator
<=> passt viel besser zur Rolle des Basiselements als der Operator
< , da beide Probleme des letzteren beseitigt werden.
Erstens
ist garantiert, dass der Ausdruck
a <= b auch bei teilweiser Ordnung äquivalent zu
(a <=> b) <= 0 ist . Für zwei ungeordnete Werte gibt
a <=> b den Wert
partielle_bestellte :: ungeordnete und
partielle_bestellte :: ungeordnete <= 0 gibt
false an , was wir brauchen. Dies ist möglich, weil
<=> mehr Arten von Werten zurückgeben kann: Beispielsweise enthält die Kategorie
partielle Ordnung vier mögliche Werte. Ein Wert vom Typ
bool kann nur
wahr oder
falsch sein , bevor wir nicht zwischen Vergleichen geordneter und ungeordneter Werte unterscheiden konnten.
Betrachten Sie zur Verdeutlichung ein Beispiel für eine Teilordnungsbeziehung, die sich nicht auf Gleitkommazahlen bezieht. Angenommen, wir möchten einem
int- Typ einen NaN-Status hinzufügen, wobei NaN nur ein Wert ist, der kein geordnetes Paar mit einem beteiligten Wert bildet. Sie können dies mit
std :: optional tun, um es zu speichern:
struct IntNan { std::optional<int> val = std::nullopt; bool operator==(IntNan const& rhs) const { if (!val || !rhs.val) { return false; } return *val == *rhs.val; } partial_ordering operator<=>(IntNan const& rhs) const { if (!val || !rhs.val) {
Der Operator
<= gibt den korrekten Wert zurück, da wir jetzt mehr Informationen auf der Ebene der Sprache selbst ausdrücken können.
Zweitens reicht es aus,
<=> einmal anzuwenden, um alle erforderlichen Informationen zu erhalten, was die Implementierung eines lexikografischen Vergleichs erleichtert:
struct A { T t; U u; bool operator==(A const& rhs) const { return t == rhs.t && u == rhs.u; } strong_ordering operator<=>(A const& rhs) const {
Weitere
Informationen finden Sie in
P0515 , dem ursprünglichen Satz zum Hinzufügen des
Operators <=>.Neue Bedienerfunktionen
Wir verfügen nicht nur über einen neuen Betreiber. Wenn das oben gezeigte Beispiel mit der Deklaration der Struktur
A nur besagt, dass
wir jetzt jedes Mal anstelle von
x <y (x <=> y) <0 schreiben müssen, würde es niemandem gefallen.
Der Mechanismus zum Auflösen von Vergleichen in C ++ 20 unterscheidet sich deutlich vom alten Ansatz, aber diese Änderung steht in direktem Zusammenhang mit dem neuen Konzept zweier grundlegender Vergleichsoperatoren:
== und
<=> . Wenn es früher eine Redewendung (Aufnahme über
== und
< ) war, die wir verwendet haben, von der der Compiler aber nichts wusste, wird er diesen Unterschied jetzt verstehen.
Ich werde noch einmal die Tabelle geben, die Sie bereits am Anfang des Artikels gesehen haben:
Jeder der Basis- und Ableitungsoperatoren hat eine neue Fähigkeit erhalten, die ich noch ein paar Worte weiter sagen werde.
Inversion von Basisoperatoren
Nehmen Sie als Beispiel einen Typ, der nur mit
int verglichen werden kann:
struct A { int i; explicit A(int i) : i(i) { } bool operator==(int j) const { return i == j; } };
Unter dem Gesichtspunkt der alten Regeln ist es nicht überraschend, dass der Ausdruck
a == 10 funktioniert und zu
a.operator == (10) ausgewertet wird.
Aber was ist mit
10 == a ? In C ++ 17 wird dieser Ausdruck als eindeutiger Syntaxfehler betrachtet. Es gibt keinen solchen Operator. Damit ein solcher Code funktioniert, müssten Sie einen symmetrischen
Operator == schreiben, der zuerst den Wert von
int und dann
A ... annimmt, und um dies zu implementieren, müsste er in Form einer freien Funktion vorliegen.
In C ++ 20 können grundlegende Operatoren invertiert werden. Für
10 == a findet der Compiler den Kandidatenoperator
== (A, int) (tatsächlich ist dies eine Mitgliedsfunktion, aber der Klarheit halber schreibe ich ihn hier als freie Funktion) und dann zusätzlich - eine Variante mit der umgekehrten Reihenfolge der Parameter, d.h. .
operator == (int, A) . Dieser zweite Kandidat stimmt mit unserem Ausdruck überein (und im Idealfall), also werden wir ihn wählen. Der Ausdruck
10 == a in C ++ 20 wird als
a.operator == (10) ausgewertet. Der Compiler versteht, dass Gleichheit symmetrisch ist.
Jetzt werden wir unseren Typ erweitern, damit er nicht nur über den Gleichheitsoperator, sondern auch über den Ordnungsoperator mit
int verglichen werden kann:
struct A { int i; explicit A(int i) : i(i) { } bool operator==(int j) const { return i == j; } strong_ordering operator<=>(int j) const { return i <=> j; } };
Auch hier funktioniert der Ausdruck
a <=> 42 einwandfrei und wird nach den alten Regeln als
a.operator <=> (42) berechnet, aber
42 <=> a wäre aus Sicht von C ++ 17 falsch, selbst wenn der Operator
< => existierte bereits in der Sprache. In C ++ 20 ist der
Operator <=> wie der
Operator == symmetrisch: Er erkennt invertierte Kandidaten. Für
42 <=> a wird ein
Elementfunktionsoperator <=> (A, int) gefunden (wieder schreibe ich es hier nur zur besseren Übersichtlichkeit als freie Funktion) sowie ein synthetischer Kandidatenoperator
<=> (int, A). . Diese umgekehrte Version passt genau zu unserem Ausdruck - wir wählen ihn aus.
42 <=> a wird jedoch NICHT als
a.operator <=> berechnet
(42) . Das wäre falsch Dieser Ausdruck
ergibt 0 <=> a.operator <=> (42) . Versuchen Sie herauszufinden, warum dieser Eintrag korrekt ist.
Es ist wichtig zu beachten, dass der Compiler keine neuen Funktionen erstellt. Bei der Berechnung von
10 == a wurde der neue Operatoroperator
== (int, A) nicht angezeigt, und bei der Berechnung von
42 <=> a wurde der
Operator <=> (int, A) nicht angezeigt. Nur zwei Ausdrücke werden durch invertierte Kandidaten umgeschrieben. Ich wiederhole: Es werden keine neuen Funktionen erstellt.
Beachten Sie auch, dass ein Datensatz mit der umgekehrten Reihenfolge der Parameter nur für Basisoperatoren verfügbar ist, für Ableitungen jedoch nicht. Also:
struct B { bool operator!=(int) const; }; b != 42;
Abgeleitete Operatoren umschreiben
Kehren wir zu unserem Beispiel mit Struktur
A zurück :
struct A { int i; explicit A(int i) : i(i) { } bool operator==(int j) const { return i == j; } strong_ordering operator<=>(int j) const { return i <=> j; } };
Nehmen Sie den Ausdruck
a! = 17 . In C ++ 17 ist dies ein Syntaxfehler, da der
Operator! = Operator nicht vorhanden ist. In C ++ 20 sucht der Compiler jedoch für Ausdrücke, die abgeleitete Vergleichsoperatoren enthalten, auch nach den entsprechenden Basisoperatoren und drückt abgeleitete Vergleiche durch diese aus.
Wir wissen, dass in der Mathematik die Operation
! = Im Wesentlichen NICHT
== bedeutet. Dies ist dem Compiler nun bekannt. Für den Ausdruck
a! = 17 sucht er nicht nur nach dem
Operator! = Operatoren , sondern auch nach dem
Operator == (und wie in den vorherigen Beispielen nach dem invertierten
Operator == ). In diesem Beispiel haben wir einen Gleichheitsoperator gefunden, der fast zu uns passt - wir müssen ihn nur entsprechend der gewünschten Semantik umschreiben:
a! = 17 wird berechnet als
! (A == 17) .
In ähnlicher Weise wird
17! = A berechnet als
! A.operator == (17) , was sowohl eine umgeschriebene als auch eine invertierte Version ist.
Ähnliche Transformationen werden auch für bestellende Operatoren durchgeführt. Wenn wir
eine <9 schreiben würden, würden wir (erfolglos) versuchen, den
Operator < zu finden, und auch die grundlegenden Kandidaten berücksichtigen: den
Operator <=> . Der entsprechende Ersatz für die Beziehungsoperatoren sieht folgendermaßen aus:
a @ b (wobei
@ einer der Beziehungsoperatoren ist) wird als
(a <=> b) @ 0 berechnet. In unserem Fall ist
a.operator <=> (9) <0 . In ähnlicher Weise wird
9 <= a als
0 <= a.operator <=> (9) berechnet.
Beachten Sie, dass der Compiler wie im Fall des Aufrufs keine neuen Funktionen für die neu geschriebenen Kandidaten erstellt. Sie werden einfach unterschiedlich berechnet und alle Transformationen werden nur auf Quellcodeebene ausgeführt.
Das Obige führt mich zu folgendem Rat:
NUR GRUNDLEGENDE OPERATOREN : Definieren Sie nur grundlegende Operatoren (== und <=>) in Ihrem Typ.Da die Basisoperatoren den gesamten Satz von Vergleichen angeben, reicht es aus, nur diese zu definieren. Dies bedeutet, dass Sie nur 2 Operatoren benötigen, um Objekte desselben Typs zu vergleichen (anstelle von 6, ab sofort), und nur 2 Operatoren, um verschiedene Objekttypen zu vergleichen (anstelle von 12). Wenn Sie nur die Gleichheitsoperation benötigen, schreiben Sie einfach 1 Funktion zum Vergleichen von Objekten desselben Typs (anstelle von 2) und 1 Funktion zum Vergleichen verschiedener Objekttypen (anstelle von 4). Die
Klasse std :: sub_match ist ein Extremfall: In C ++ 17 werden 42 Vergleichsoperatoren verwendet, und in C ++ 20 werden nur 8 verwendet, während die Funktionalität in keiner Weise darunter leidet.
Da der Compiler auch invertierte Kandidaten berücksichtigt, können alle diese Operatoren als Elementfunktionen implementiert werden. Sie müssen keine freien Funktionen mehr schreiben, um Objekte verschiedener Typen zu vergleichen.
Sonderregeln für die Suche nach Kandidaten
Wie bereits erwähnt, wurde die Suche nach Kandidaten für
a @ b in C ++ 17 nach folgendem Prinzip durchgeführt: Wir finden alle
Operator @ -Operatoren und wählen aus ihnen den am besten geeigneten aus.
C ++ 20 verwendet eine erweiterte Gruppe von Kandidaten. Jetzt werden wir alle
Operatoren @ suchen. Sei
@@ der
Basisoperator für
@ (es kann der gleiche Operator sein). Wir finden auch alle
Operatoren @@ und
fügen für jeden von ihnen die invertierte Version hinzu. Aus all diesen gefundenen Kandidaten wählen wir die am besten geeigneten aus.
Beachten Sie, dass eine Überladung des Bedieners in
einem einzigen Durchgang zulässig ist. Wir versuchen nicht, verschiedene Kandidaten zu ersetzen. Zuerst sammeln wir sie alle und wählen dann die beste aus ihnen aus. Wenn dies nicht vorhanden ist, schlägt die Suche wie zuvor fehl.
Jetzt haben wir viel mehr potenzielle Kandidaten und damit mehr Unsicherheit. Betrachten Sie das folgende Beispiel:
struct C { bool operator==(C const&) const; bool operator!=(C const&) const; }; bool check(C x, C y) { return x != y; }
In C ++ 17 hatten wir nur einen Kandidaten für
x! = Y , und jetzt gibt es drei:
x.operator! = (Y) ,! X.operator == (y) und
! Y.operator == (x) . Was soll ich wählen? Sie sind alle gleich! (Hinweis: Der Kandidat
y.operator! = (X) existiert nicht, da nur
Basisoperatoren invertiert werden können .)
Zwei zusätzliche Regeln wurden eingeführt, um diese Unsicherheit zu beseitigen. Nicht konvertierte Kandidaten sind Konvertiten vorzuziehen. . ,
x.operator!=(y) «»
!x.operator==(y) , «»
!y.operator==(x) . , «» .
:
operator@@ . . , .
-. — (,
x < y , —
(x <=> y) < 0 ), (,
x <=> y void - , DSL), . . ,
bool ( :
operator== bool , ?)
Zum Beispiel:
struct Base { friend bool operator<(const Base&, const Base&);
d1 < d2 :
#1 #2 . —
#2 , , , . ,
d1 < d2 (d1 <=> d2) < 0 . ,
void 0 — , . , - ,
#1 .
, , C++17, . , - . :
, . .
. , , , , , ( ). , :
« » , , ..
a < b 0 < (b <=> a) , , , .
C++17 . . :
struct A { T t; U u; V v; bool operator==(A const& rhs) const { return t == rhs.t && u == rhs.u && v == rhs.v; } bool operator!=(A const& rhs) const { return !(*this == rhs); } bool operator< (A const& rhs) const {
-
std::tie() , .
, : :
struct A { T t; U u; V v; bool operator==(A const& rhs) const { return t == rhs.t && u == rhs.u && v == rhs.v; } strong_ordering operator<=>(A const& rhs) const {
.
<=> < . , .
c != 0 , , (
), .
. C++20 , :
struct A { T t; U u; V v; bool operator==(A const& rhs) const = default; strong_ordering operator<=>(A const& rhs) const = default; };
, . , :
struct A { T t; U u; V v; bool operator==(A const& rhs) const = default; auto operator<=>(A const& rhs) const = default; };
. , , :
struct A { T t; U u; V v; auto operator<=>(A const& rhs) const = default; };
, , . :
operator== ,
operator<=> .
C++20: . . , , , .
PVS-Studio , <=> . , -. , , (. "
"). ++ .
PVS-Studio <, :
bool operator< (A const& rhs) const { return t < rhs.t && u < rhs.u; }
. , - . .
:
Comparisons in C++20 .