Оператор равенства не определен для пользовательской реализации оператора космического корабля в C ++ 20

51

Я столкнулся со странным поведением с новым оператором космического корабля <=>в C ++ 20. Я использую компилятор Visual Studio 2019 с /std:c++latest.

Этот код компилируется нормально, как и ожидалось:

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

Однако, если я изменю X на это:

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

Я получаю следующую ошибку компилятора:

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

Я попробовал это и на Clang, и у меня похожее поведение.

Я был бы признателен за объяснение того, почему реализация по умолчанию генерируется operator==правильно, а пользовательская - нет.

Zeenobit
источник

Ответы:

50

Это по замыслу.

[class.compare.default] (выделено мое)

3 Если определение класса не объявляет явно == операторную функцию, но объявляет дефолтную функцию оператора трехстороннего сравнения , ==операторная функция объявляется неявно с тем же доступом, что и функция оператора трехстороннего сравнения. Неявно объявленный ==оператор для класса X является встроенным членом и определяется как значение по умолчанию в определении X.

Только дефолт <=>позволяет синтезированному ==существовать. Смысл в том, что классы вроде std::vectorне могут использовать дефолт <=>. Кроме того, использование <=>for ==- не самый эффективный способ сравнения векторов. <=>должен дать точный порядок, тогда как ==может освободить залог рано, сравнивая размеры в первую очередь.

Если класс делает что-то особенное в своем трехстороннем сравнении, ему, вероятно, потребуется сделать что-то особенное в своем ==. Таким образом, вместо того, чтобы генерировать бессмысленное значение по умолчанию, язык оставляет его на усмотрение программиста.

Рассказчик - Unslander Monica
источник
4
Это, конечно, разумно, если только космический корабль не глючит. Потенциально крайне неэффективно, хотя ...
Дедупликатор
1
@Deduplicator - чувствительность субъективна. Некоторые скажут, что тихо сгенерированная неэффективная реализация не имеет смысла.
StoryTeller - Unslander Моника
45

Во время стандартизации этой функции было решено, что равенство и порядок должны быть логически разделены. Таким образом, использование проверки на равенство ( ==и !=) никогда не вызовет operator<=>. Тем не менее, все еще считалось полезным иметь возможность использовать их по умолчанию в одном объявлении. Поэтому, если вы по умолчанию operator<=>, было решено, что вы также имели в виду по умолчанию operator==(если вы не определили его позже или определили его ранее).

Относительно того, почему было принято это решение , основные рассуждения таковы. Посмотрим std::string. Порядок двух строк лексикографический; каждый символ имеет свое целочисленное значение по сравнению с каждым символом в другой строке. Первое неравенство приводит к результату упорядочения.

Однако проверка равенства строк имеет короткое замыкание. Если две строки не имеют одинаковую длину, тогда нет смысла делать посимвольное сравнение вообще; они не равны. Поэтому, если кто-то проводит тестирование на равенство, вы не хотите делать его длинным, если можете его замкнуть.

Оказывается, что многие типы, которым требуется пользовательский порядок, также предлагают некоторый механизм короткого замыкания для проверки на равенство. Чтобы люди не реализовывали operator<=>и не отбрасывали потенциальную производительность, мы эффективно заставляем всех делать то и другое.

Николь Болас
источник
5
Это гораздо лучшее объяснение, чем принятый ответ
записка от
17

Другие ответы очень хорошо объясняют, почему язык такой. Я просто хотел добавить, что в случае, если это не очевидно, конечно, возможно предоставить пользователю operator<=>дефолт operator==. Вам просто нужно явно написать значение по умолчанию operator==:

struct X
{
    int Dummy = 0;
    auto operator<=>(const X& other) const
    {
        return Dummy <=> other.Dummy;
    }
    bool operator==(const X& other) const = default;
};
Oktalist
источник