Operator równości nie został zdefiniowany dla niestandardowej implementacji operatora statku kosmicznego w C ++ 20

51

Napotykam dziwne zachowanie z nowym operatorem statku kosmicznego <=>w C ++ 20. Korzystam z kompilatora Visual Studio 2019 z /std:c++latest.

Ten kod kompiluje się zgodnie z oczekiwaniami:

#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;
}

Jeśli jednak zmienię X na to:

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

Otrzymuję następujący błąd kompilatora:

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

Próbowałem tego również na clang i mam podobne zachowanie.

Byłbym wdzięczny za wyjaśnienie, dlaczego domyślna implementacja generuje operator==poprawnie, ale niestandardowa nie.

Zeenobit
źródło

Odpowiedzi:

50

To jest z założenia.

[class.compare.default] ( wyróżnienie moje)

3 Jeśli definicja klasy nie deklaruje jawnie == funkcji operatora, ale deklaruje domyślną funkcję operatora porównania trójstronnego, funkcja ==operatora jest zadeklarowana domyślnie z takim samym dostępem, jak funkcja operatora porównania trójstronnego. Operator niejawnie zadeklarowany ==dla klasy X jest elementem wbudowanym i jest zdefiniowany jako domyślny w definicji X.

Tylko domyślna <=>pozwala na istnienie syntezatora ==. Uzasadnieniem jest to, że klasy takie jak std::vectornie mogą używać domyślnej <=>. Ponadto użycie <=>for ==nie jest najskuteczniejszym sposobem porównywania wektorów. <=>musi podać dokładne zamówienie, ale ==może wcześniej zapłacić kaucję, porównując najpierw rozmiary.

Jeśli klasa zrobi coś specjalnego w swoim trójstronnym porównaniu, prawdopodobnie będzie musiała zrobić coś specjalnego ==. Dlatego zamiast generować nie rozsądne ustawienie domyślne, język pozostawia to programistom.

StoryTeller - Unslander Monica
źródło
4
Jest to z pewnością rozsądne, chyba że statek kosmiczny jest wadliwy. Potencjalnie rażąco nieefektywny ...
Deduplicator
1
@Deduplicator - Wrażliwość jest subiektywna. Niektórzy twierdzą, że cicho generowana nieefektywna implementacja nie jest rozsądna.
StoryTeller - Unslander Monica,
45

Podczas standaryzacji tej funkcji zdecydowano, że równość i porządek powinny być logicznie rozdzielone. W związku z tym zastosowania testów równości ( ==i !=) nigdy nie będą wywoływane operator<=>. Jednak nadal uważano za użyteczne możliwość domyślnego ustawienia obu z nich za pomocą jednej deklaracji. Więc jeśli domyślnie operator<=>, zdecydowano, że masz również zamiar domyślnieoperator== (chyba że zdefiniujesz to później lub wcześniej zdefiniowałeś).

Jak się dlaczego ta decyzja została podjęta , podstawowe rozumowanie wygląda następująco. Zastanów się std::string. Porządkowanie dwóch łańcuchów jest leksykograficzne; każdy znak ma wartość całkowitą w porównaniu z każdym znakiem w drugim ciągu. Pierwsza nierówność skutkuje wynikiem zamówienia.

Jednak testowanie równości ciągów ma zwarcie. Jeśli dwa ciągi nie są równej długości, nie ma sensu dokonywać porównania pod względem znaków; nie są równi. Więc jeśli ktoś przeprowadza testy równości, nie chcesz robić tego długo, jeśli możesz to zwierać.

Okazuje się, że wiele typów, które wymagają uporządkowania zdefiniowanego przez użytkownika, będzie również oferowało pewien mechanizm zwarciowy do testowania równości. Aby uniemożliwić ludziom wdrażanie tylko operator<=>i wyrzucanie potencjalnej wydajności, skutecznie zmuszamy wszystkich do zrobienia obu rzeczy.

Nicol Bolas
źródło
5
Jest to znacznie lepsze wytłumaczenie niż przyjęta odpowiedź
notatka z
17

Pozostałe odpowiedzi naprawdę dobrze wyjaśniają, dlaczego ten język jest taki. Chciałem tylko dodać, że w przypadku, gdy nie jest to oczywiste, możliwe jest, aby użytkownik otrzymał operator<=>domyślnie operator==. Musisz tylko napisać domyślnie operator==:

struct X
{
    int Dummy = 0;
    auto operator<=>(const X& other) const
    {
        return Dummy <=> other.Dummy;
    }
    bool operator==(const X& other) const = default;
};
Oktalista
źródło