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

Я сталкиваюсь со странным поведением с новым оператором космического корабля <=> в 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== правильно, а пользовательская - нет.

Ответы [ 3 ]

43 голосов
/ 09 ноября 2019

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

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

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

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

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

38 голосов
/ 09 ноября 2019

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

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

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

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

13 голосов
/ 11 ноября 2019

Другие ответы очень хорошо объясняют, почему язык такой. Я просто хотел добавить, что в случае, если это не очевидно, конечно, возможно иметь предоставленный пользователем 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;
};
...