O operador de igualdade não é definido para uma implementação de operador de nave espacial personalizada em C ++ 20

51

Estou tendo um comportamento estranho com o novo operador de nave espacial <=>em C ++ 20. Estou usando o compilador do Visual Studio 2019 com /std:c++latest.

Esse código compila bem, conforme o esperado:

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

No entanto, se eu alterar X para isso:

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

Eu recebo o seguinte erro do compilador:

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

Eu tentei isso no clang também, e recebo um comportamento semelhante.

Gostaria de receber algumas explicações sobre por que a implementação padrão gera operator==corretamente, mas a personalizada não.

Zeenobit
fonte

Respostas:

50

Isso ocorre por design.

[class.compare.default] (ênfase minha)

3 Se a definição de classe não declarar explicitamente uma == função de operador, mas declarar uma função de operador de comparação de três vias padrão , uma ==função de operador será declarada implicitamente com o mesmo acesso que a função de operador de comparação de três vias. O ==operador declarado implicitamente para uma classe X é um membro embutido e é definido como padrão na definição de X.

Somente um padrão <=>permite que um sintetizado ==exista. A lógica é que classes como std::vectornão podem usar um padrão <=>. Além disso, usando <=>para ==não é a forma mais eficiente para comparar vetores. <=>deve dar a ordem exata, enquanto que ==pode ser resgatado cedo comparando os tamanhos primeiro.

Se uma classe faz algo especial em sua comparação de três vias, provavelmente precisará fazer algo especial nela ==. Assim, em vez de gerar um padrão não sensível, a linguagem deixa para o programador.

Contador de Histórias - Monica Sem Calúnia
fonte
4
É certamente sensato, a menos que a nave espacial seja de buggy. Potencialmente grosseiramente embora ineficiente ...
Deduplicator
11
@Duplicador - A sensibilidade é subjetiva. Alguns diriam que uma implementação ineficiente gerada silenciosamente não é sensata.
StoryTeller - Unslander Monica 11/11/19
45

Durante a padronização desse recurso, foi decidido que a igualdade e a ordem deveriam ser logicamente separadas. Como tal, os usos do teste de igualdade ( ==e !=) nunca serão invocados operator<=>. No entanto, ainda era considerado útil poder padronizar os dois com uma única declaração. Portanto, se você usar o padrão operator<=>, foi decidido que você também deveria usar o padrão operator==(a menos que você o defina mais tarde ou o defina anteriormente).

Quanto ao motivo pelo qual essa decisão foi tomada , o raciocínio básico é o seguinte. Considere std::string. A ordenação de duas strings é lexicográfica; cada caractere tem seu valor inteiro comparado a cada caractere na outra sequência. A primeira desigualdade resulta no resultado do pedido.

No entanto, o teste de igualdade de strings tem um curto-circuito. Se as duas seqüências não tiverem o mesmo comprimento, não faz sentido fazer uma comparação entre caracteres; eles não são iguais. Portanto, se alguém estiver testando a igualdade, você não deseja fazê-lo de forma longa, se conseguir um curto-circuito.

Acontece que muitos tipos que precisam de uma ordem definida pelo usuário também oferecerão algum mecanismo de curto-circuito para teste de igualdade. Para impedir que as pessoas implementem apenas operator<=>e joguem fora o desempenho potencial, forçamos efetivamente todos a fazer as duas coisas.

Nicol Bolas
fonte
5
Esta é uma explicação muito melhor do que a resposta aceita
memo
17

As outras respostas explicam muito bem por que a linguagem é assim. Eu só queria acrescentar que, caso não seja óbvio, é claro que é possível ter operator<=>um padrão fornecido pelo usuário operator==. Você só precisa escrever explicitamente o padrão operator==:

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