Standardvergleiche (seit C++20)
Vergleichsoperatorfunktionen können explizit als standardmäßig deklariert werden, um den Compiler anzuweisen, den entsprechenden Standardvergleich für eine Klasse zu generieren.
Inhalt |
[edit] Definition
Eine standardmäßig deklarierte Vergleichsoperatorfunktion ist eine nicht-template Vergleichsoperatorfunktion (d.h. <=>, ==, !=, <, >, <= oder >=), die alle folgenden Bedingungen erfüllt:
- Sie ist ein nicht-statisches Mitglied oder friend einer Klasse
C. - Sie ist in
Coder in einem Kontext, in demCein unvollständiger Typ ist, als standardmäßig deklariert. - Sie hat zwei Parameter vom Typ const C& oder zwei Parameter vom Typ
C, wobei der implizite Objektparameter (falls vorhanden) als erster Parameter betrachtet wird.
Eine solche Vergleichsoperatorfunktion wird als standardmäßig deklarierte Vergleichsoperatorfunktion für die Klasse C bezeichnet.
struct X { bool operator==(const X&) const = default; // OK bool operator==(const X&) = default; // Error: the implicit object // parameter type is X& bool operator==(this X, X) = default; // OK }; struct Y { friend bool operator==(Y, Y) = default; // OK friend bool operator==(Y, const Y&) = default; // Error: different parameter types }; bool operator==(const Y&, const Y&) = default; // Error: not a friend of Y
Namenssuchen und Zugriffsprüfungen in der impliziten Definition einer Vergleichsoperatorfunktion werden aus einem Kontext durchgeführt, der ihrem Funktionsrumpf entspricht. Eine als standardmäßig deklarierte Definition einer Vergleichsoperatorfunktion, die in einer Klasse erscheint, muss die erste Deklaration dieser Funktion sein.
[edit] Standardvergleichsreihenfolge
Gegeben sei eine Klasse C; eine Liste von Unterobjekten wird in der folgenden Reihenfolge gebildet:
- Die direkten Basisklassen-Unterobjekte von
C, in deklarierter Reihenfolge. - Die nicht-statischen Datenmitglieder von
C, in deklarierter Reihenfolge.
- Wenn ein Mitgliedsunterobjekt vom Array-Typ ist, wird es auf die Sequenz seiner Elemente erweitert, in der Reihenfolge steigender Indizes. Die Erweiterung ist rekursiv: Array-Elemente von Array-Typen werden erneut erweitert, bis kein Unterobjekt vom Array-Typ mehr vorhanden ist.
Für jedes Objekt x vom Typ C gilt in den folgenden Beschreibungen:
- Sei n die Anzahl der Unterobjekte in der (erweiterten) Unterobjektliste für x.
- Sei x_i das i-te Unterobjekt in der (erweiterten) Unterobjektliste für x, wobei x_i durch eine Sequenz von Impliziten Konvertierungen von abgeleitetem zu Basis-Typ, Zugriffs-Ausdrücken auf Klassenmitglieder und Array-Index-Ausdrücken, angewendet auf x, gebildet wird.
struct S {}; struct T : S { int arr[2][2]; } t; // The subobject list for “t” consists of the following 5 subobjects in order: // (S)t → t[0][0] → t[0][1] → t[1][0] → t[1][1]
[edit] Drei-Wege-Vergleich
Ein operator<=> für einen Klassentyp kann mit jedem beliebigen Rückgabetyp als standardmäßig deklariert werden.
[edit] Vergleichskategorietypen
Es gibt drei Vergleichskategorietypen:
| Typ | Äquivalente Werte sind.. | Nicht vergleichbare Werte sind.. |
|---|---|---|
| std::strong_ordering | ununterscheidbar | nicht erlaubt |
| std::weak_ordering | unterscheidbar | nicht erlaubt |
| std::partial_ordering | unterscheidbar | erlaubt |
[edit] Synthetisierter Drei-Wege-Vergleich
Der synthetisierte Drei-Wege-Vergleich vom Typ T zwischen Glvalues a und b desselben Typs ist wie folgt definiert:
- Wenn die Überladungsauflösung für a <=> b einen verwendbaren Kandidaten ergibt und explizit zu
Tmittelsstatic_castkonvertiert werden kann, ist der synthetisierte Vergleich static_cast<T>(a <=> b). - Andernfalls, wenn eine der folgenden Bedingungen erfüllt ist, ist der synthetisierte Vergleich nicht definiert:
- Die Überladungsauflösung für a <=> b findet mindestens einen zulässigen Kandidaten.
-
Tist kein Vergleichskategorietyp. - Die Überladungsauflösung für a == b ergibt keinen verwendbaren Kandidaten.
- Die Überladungsauflösung für a < b ergibt keinen verwendbaren Kandidaten.
- Andernfalls, wenn
Tstd::strong_ordering ist, ist der synthetisierte Vergleich:
a == b ? std::strong_ordering::equal : a < b ? std::strong_ordering::less : std::strong_ordering::greater
- Andernfalls, wenn
Tstd::weak_ordering ist, ist der synthetisierte Vergleich:
a == b ? std::weak_ordering::equivalent : a < b ? std::weak_ordering::less : std::weak_ordering::greater
- Andernfalls (
Tist std::partial_ordering), ist der synthetisierte Vergleich:
a == b ? std::partial_ordering::equivalent : a < b ? std::partial_ordering::less : b < a ? std::partial_ordering::greater : std::partial_ordering::unordered
[edit] Platzhalter-Rückgabetyp
Wenn der deklarierte Rückgabetyp einer standardmäßig deklarierten Drei-Wege-Vergleichsoperatorfunktion (operator<=>) für einen Klassentyp C auto ist, wird der Rückgabetyp aus den Rückgabetypen der Drei-Wege-Vergleiche zwischen den entsprechenden Unterobjekten eines Objekts x vom Typ C abgeleitet.
Für jedes Unterobjekt x_i in der (erweiterten) Unterobjektliste für x:
- Führen Sie eine Überladungsauflösung für x_i <=> x_i durch. Wenn die Überladungsauflösung keinen verwendbaren Kandidaten ergibt, wird der standardmäßig deklarierte operator<=> als gelöscht definiert.
- Bezeichnen Sie die cv-unqualifizierte Version des Typs von x_i <=> x_i als
R_i. WennR_ikein Vergleichskategorietyp ist, wird der standardmäßig deklarierte operator<=> als gelöscht definiert.
Wenn der standardmäßig deklarierte operator<=> nicht als gelöscht definiert ist, wird sein Rückgabetyp als std::common_comparison_category_t<R_1, R_2, ..., R_n> abgeleitet.
[edit] Nicht-Platzhalter-Rückgabetyp
Wenn der deklarierte Rückgabetyp des standardmäßig deklarierten operator<=> nicht auto ist, darf er keinen Platzhaltertyp enthalten (z.B. decltype(auto)).
Wenn es ein Unterobjekt x_i in der (erweiterten) Unterobjektliste für x gibt, für das der synthetisierte Drei-Wege-Vergleich des deklarierten Rückgabetyps zwischen x_i und x_i nicht definiert ist, wird der standardmäßig deklarierte operator<=> als gelöscht definiert.
[edit] Vergleichsergebnis
Seien x und y die Parameter eines standardmäßig deklarierten operator<=>. Bezeichnen Sie jedes Unterobjekt in der (erweiterten) Unterobjektliste für x und y als x_i bzw. y_i. Der standardmäßige Drei-Wege-Vergleich zwischen x und y wird durch den Vergleich entsprechender Unterobjekte x_i und y_i in aufsteigender i-Reihenfolge durchgeführt.
Sei R der (möglicherweise abgeleitete) Rückgabetyp. Das Vergleichsergebnis zwischen x_i und y_i ist das Ergebnis des synthetisierten Drei-Wege-Vergleichs vom Typ R zwischen x_i und y_i.
- Während des standardmäßigen Drei-Wege-Vergleichs zwischen x und y, wenn ein unterobjektweiser Vergleich zwischen x_i und y_i ein Ergebnis v_i erzeugt, so dass die kontextuelle Konvertierung von v_i != 0 zu bool true ergibt, ist der Rückgabewert eine Kopie von v_i (die restlichen Unterobjekte werden nicht verglichen).
- Andernfalls ist der Rückgabewert static_cast<R>(std::strong_ordering::equal).
#include <compare> #include <iostream> #include <set> struct Point { int x; int y; auto operator<=>(const Point&) const = default; /* non-comparison functions */ }; int main() { Point pt1{1, 1}, pt2{1, 2}; std::set<Point> s; // OK s.insert(pt1); // OK // two-way comparison operator functions are not required to be explicitly defined: // operator== is implicitly declared (see below) // the overload resolutions of other candidates will select rewritten candidates std::cout << std::boolalpha << (pt1 == pt2) << ' ' // false << (pt1 != pt2) << ' ' // true << (pt1 < pt2) << ' ' // true << (pt1 <= pt2) << ' ' // true << (pt1 > pt2) << ' ' // false << (pt1 >= pt2) << ' '; // false }
[edit] Gleichheitsvergleich
[edit] Explizite Deklaration
Ein operator== für einen Klassentyp kann mit dem Rückgabetyp bool als standardmäßig deklariert werden.
Gegeben sei eine Klasse C und ein Objekt x vom Typ C. Wenn es ein Unterobjekt x_i in der (erweiterten) Unterobjektliste für x gibt, für das die Überladungsauflösung für x_i == x_i keinen verwendbaren Kandidaten ergibt, wird der standardmäßig deklarierte operator== als gelöscht definiert.
Seien x und y die Parameter eines standardmäßig deklarierten operator==. Bezeichnen Sie jedes Unterobjekt in der (erweiterten) Unterobjektliste für x und y als x_i bzw. y_i. Der standardmäßige Gleichheitsvergleich zwischen x und y wird durch den Vergleich entsprechender Unterobjekte x_i und y_i in aufsteigender i-Reihenfolge durchgeführt.
Das Vergleichsergebnis zwischen x_i und y_i ist das Ergebnis von x_i == y_i.
- Während des standardmäßigen Gleichheitsvergleichs zwischen x und y, wenn ein unterobjektweiser Vergleich zwischen x_i und y_i ein Ergebnis v_i erzeugt, so dass die kontextuelle Konvertierung von v_i zu bool false ergibt, ist der Rückgabewert false (die restlichen Unterobjekte werden nicht verglichen).
- Andernfalls ist der Rückgabewert true.
#include <iostream> struct Point { int x; int y; bool operator==(const Point&) const = default; /* non-comparison functions */ }; int main() { Point pt1{3, 5}, pt2{2, 5}; std::cout << std::boolalpha << (pt1 != pt2) << '\n' // true << (pt1 == pt1) << '\n'; // true struct [[maybe_unused]] { int x{}, y{}; } p, q; // if (p == q) {} // Error: operator== is not defined }
[edit] Implizite Deklaration
Wenn eine Klasse C keine Mitglieds- oder Freundesfunktion namens operator== explizit deklariert, wird für jeden als standardmäßig deklarierten operator<=> implizit eine Operatorfunktion deklariert. Jede implizit deklarierte operator== hat denselben Zugriff und dieselbe Funktionsdefinition und befindet sich im selben Gültigkeitsbereich der Klasse wie die entsprechende standardmäßig deklarierte operator<=>, mit den folgenden Änderungen:
- Der Deklarator-Identifikator wird durch operator== ersetzt.
- Der Rückgabetyp wird durch bool ersetzt.
template<typename T> struct X { friend constexpr std::partial_ordering operator<=>(X, X) requires (sizeof(T) != 1) = default; // implicitly declares: friend constexpr bool operator==(X, X) // requires (sizeof(T) != 1) = default; [[nodiscard]] virtual std::strong_ordering operator<=>(const X&) const = default; // implicitly declares: [[nodiscard]] virtual bool // operator==(const X&) const = default; };
[edit] Sekundärvergleich
Eine sekundäre Vergleichsoperatorfunktion (!=, <, >, <= oder >=) für einen Klassentyp kann mit dem Rückgabetyp bool als standardmäßig deklariert werden.
Sei @ einer der fünf sekundären Vergleichsoperatoren. Für jeden standardmäßig deklarierten operator@ mit den Parametern x und y werden bis zu zwei Überladungsauflösungen durchgeführt (ohne den standardmäßig deklarierten operator@ als Kandidaten zu betrachten), um zu bestimmen, ob er als gelöscht definiert wird.
- Die erste Überladungsauflösung wird für x @ y durchgeführt. Wenn die Überladungsauflösung keinen verwendbaren Kandidaten ergibt oder der ausgewählte Kandidat keine umgeschriebene Kandidatenfunktion ist, wird der standardmäßig deklarierte operator@ als gelöscht definiert. In diesen Fällen erfolgt keine zweite Überladungsauflösung.
- Die zweite Überladungsauflösung wird für den ausgewählten umgeschriebenen Kandidaten von x @ y durchgeführt. Wenn die Überladungsauflösung keinen verwendbaren Kandidaten ergibt, wird der standardmäßig deklarierte operator@ als gelöscht definiert.
Wenn x @ y nicht implizit in bool konvertiert werden kann, wird der standardmäßig deklarierte operator@ als gelöscht definiert.
Wenn der standardmäßig deklarierte operator@ nicht als gelöscht definiert ist, liefert er x @ y.
struct HasNoRelational {}; struct C { friend HasNoRelational operator<=>(const C&, const C&); bool operator<(const C&) const = default; // OK, function is defaulted };
[edit] Schlüsselwörter
[edit] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 2539 | C++20 | der synthetisierte Drei-Wege-Vergleich würde wählen static_cast, auch wenn die explizite Konvertierung nicht verfügbar ist |
wählt nicht static_cast in diesem Fall |
| CWG 2546 | C++20 | der standardmäßig deklarierte sekundäre operator@ war nicht als gelöscht definiert, wenn die Überladungsauflösung von x @ y einen nicht verwendbaren umgeschriebenen Kandidaten auswählt |
als gelöscht definiert in diesem Fall |
| CWG 2547 | C++20 | es war unklar, ob Vergleichsoperator Funktionen für Nicht-Klassen standardmäßig deklariert werden können |
sie können nicht standardmäßig deklariert werden |
| CWG 2568 | C++20 | die implizite Definition von Vergleichsoperator Funktionen könnte gegen Mitgliedszugriffsregeln verstoßen |
Zugriffsprüfungen werden durchgeführt aus einem Kontext, der äquivalent zu ihren Funktionsrümpfen ist |
[edit] Siehe auch
- Überladungsauflösung bei einem Aufruf eines überladenen Operators
- Eingebauter Drei-Wege-Vergleichsoperator
- Operatorüberladung für Vergleichsoperatoren