Der Gleichheitsoperator wird für eine Implementierung eines benutzerdefinierten Raumschiffoperators in C ++ 20 nicht definiert

51

Ich habe ein seltsames Verhalten mit dem neuen Raumschiffoperator <=>in C ++ 20. Ich verwende den Visual Studio 2019-Compiler mit /std:c++latest.

Dieser Code wird wie erwartet gut kompiliert:

#include <compare>

struct X
{
    int Dummy = 0;
    auto operator<=>(const X&) const = default; // Default implementation
};

int main()
{
    X a, b;

    a == b; // OK!

    return 0;
}

Wenn ich jedoch X in Folgendes ändere :

struct X
{
    int Dummy = 0;
    auto operator<=>(const X& other) const
    {
        return Dummy <=> other.Dummy;
    }
};

Ich erhalte den folgenden Compilerfehler:

error C2676: binary '==': 'X' does not define this operator or a conversion to a type acceptable to the predefined operator

Ich habe das auch bei Clang versucht und bekomme ein ähnliches Verhalten.

Ich würde mich über eine Erklärung freuen, warum die Standardimplementierung operator==korrekt generiert wird, die benutzerdefinierte jedoch nicht.

Zeenobit
quelle

Antworten:

50

Dies ist beabsichtigt.

[class.compare.default] (Hervorhebung von mir)

3 Wenn die Klassendefinition keine == Operatorfunktion explizit deklariert , sondern eine standardmäßige Drei-Wege-Vergleichsoperatorfunktion deklariert , wird eine ==Operatorfunktion implizit mit demselben Zugriff wie die Drei-Wege-Vergleichsoperatorfunktion deklariert. Der implizit deklarierte ==Operator für eine Klasse X ist ein Inline-Element und wird in der Definition von X als Standard definiert.

Nur eine Standardeinstellung <=>erlaubt ==die Existenz einer Synthese . Das Grundprinzip ist, dass Klassen wie std::vectorkeine Standardeinstellungen verwenden können <=>. Darüber hinaus ist die Verwendung von <=>for ==nicht die effizienteste Methode zum Vergleichen von Vektoren. <=>muss die genaue Reihenfolge angeben, wohingegen ==möglicherweise vorzeitig gegen Kaution gehen kann, indem zuerst die Größen verglichen werden.

Wenn eine Klasse in ihrem Drei-Wege-Vergleich etwas Besonderes tut, muss sie wahrscheinlich etwas Besonderes in ihrem Drei-Wege-Vergleich tun ==. Anstatt einen unsinnigen Standard zu generieren, überlässt die Sprache dies dem Programmierer.

Geschichtenerzähler - Unslander Monica
quelle
4
Es ist sicherlich sinnvoll, es sei denn, das Raumschiff ist fehlerhaft. Möglicherweise jedoch äußerst ineffizient ...
Deduplikator
1
@Deduplicator - Sensibilität ist subjektiv. Einige würden sagen, dass eine stillschweigend erzeugte ineffiziente Implementierung nicht sinnvoll ist.
Geschichtenerzähler - Unslander Monica
45

Während der Standardisierung dieser Funktion wurde entschieden, dass Gleichheit und Reihenfolge logisch getrennt werden sollten. Daher wird die Verwendung von Gleichheitstests ( ==und !=) niemals aufgerufen operator<=>. Es wurde jedoch immer noch als nützlich angesehen, beide mit einer einzigen Deklaration als Standard festlegen zu können. Wenn Sie also einen Standardwert festlegen operator<=>, wurde entschieden, dass Sie auch einen Standardwert operator==festlegen möchten (es sei denn, Sie definieren ihn später oder haben ihn früher definiert).

Die Gründe für diese Entscheidung lauten wie folgt: Überlegen Sie std::string. Die Reihenfolge von zwei Zeichenfolgen ist lexikografisch. Jedes Zeichen hat einen ganzzahligen Wert, der mit jedem Zeichen in der anderen Zeichenfolge verglichen wird. Die erste Ungleichung führt zum Ergebnis der Bestellung.

Die Gleichheitsprüfung von Strings weist jedoch einen Kurzschluss auf. Wenn die beiden Zeichenfolgen nicht gleich lang sind, macht es keinen Sinn, einen zeichenweisen Vergleich durchzuführen. Sie sind nicht gleich. Wenn also jemand Gleichheitstests durchführt, möchten Sie dies nicht in Langform tun, wenn Sie es kurzschließen können.

Es stellt sich heraus, dass viele Typen, die eine benutzerdefinierte Reihenfolge benötigen, auch einen Kurzschlussmechanismus für Gleichheitstests bieten. Um zu verhindern, dass Menschen nur implementieren operator<=>und potenzielle Leistung wegwerfen, zwingen wir alle effektiv dazu, beides zu tun.

Nicol Bolas
quelle
5
Dies ist eine viel bessere Erklärung als die akzeptierte Antwort
Memo
17

Die anderen Antworten erklären wirklich gut, warum die Sprache so ist. Ich wollte nur hinzufügen, dass es für den Fall, dass es nicht offensichtlich ist, natürlich möglich ist, dass ein Benutzer operator<=>eine Standardeinstellung erhält operator==. Sie müssen nur explizit die Standardeinstellung schreiben operator==:

struct X
{
    int Dummy = 0;
    auto operator<=>(const X& other) const
    {
        return Dummy <=> other.Dummy;
    }
    bool operator==(const X& other) const = default;
};
Oktalist
quelle