L'opérateur d'égalité n'est pas défini pour une implémentation d'opérateur de vaisseau spatial personnalisé en C ++ 20

51

Je rencontre un comportement étrange avec le nouvel opérateur de vaisseau spatial <=>en C ++ 20. J'utilise le compilateur Visual Studio 2019 avec /std:c++latest.

Ce code se compile très bien, comme prévu:

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

Cependant, si je change X en ceci:

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

J'obtiens l'erreur de compilation suivante:

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

J'ai également essayé ceci sur clang, et j'obtiens un comportement similaire.

J'apprécierais quelques explications sur la raison pour laquelle l'implémentation par défaut génère operator==correctement, mais pas celle personnalisée.

Zeenobit
la source

Réponses:

50

C'est par conception.

[class.compare.default] (c'est moi qui souligne)

3 Si la définition de classe ne déclare pas explicitement une == fonction d'opérateur, mais déclare une fonction d' opérateur de comparaison à trois voies par défaut , une ==fonction d'opérateur est déclarée implicitement avec le même accès que la fonction d'opérateur de comparaison à trois voies. L' ==opérateur implicitement déclaré pour une classe X est un membre en ligne et est défini par défaut dans la définition de X.

Seul un défaut <=>permet à un synthétisé ==d'exister. La raison est que des classes comme std::vectorne peuvent pas utiliser un défaut <=>. De plus, l'utilisation de <=>for ==n'est pas le moyen le plus efficace de comparer des vecteurs. <=>doit donner la commande exacte, alors que ==peut caution tôt en comparant les tailles en premier.

Si une classe fait quelque chose de spécial dans sa comparaison à trois, elle devra probablement faire quelque chose de spécial dans son ==. Ainsi, au lieu de générer un défaut non sensible, le langage laisse le soin au programmeur.

Conteur - Unslander Monica
la source
4
C'est certainement raisonnable, à moins que le vaisseau spatial ne soit buggé. Potentiellement extrêmement inefficace ...
Déduplicateur
1
@Deduplicator - La sensibilité est subjective. Certains diraient qu'une mise en œuvre inefficace générée silencieusement n'est pas sensée.
Conteur - Unslander Monica
45

Lors de la standardisation de cette fonctionnalité, il a été décidé que l'égalité et l'ordre devraient être logiquement séparés. En tant que tel, les utilisations des tests d'égalité ( ==et !=) ne seront jamais invoquées operator<=>. Cependant, il était toujours considéré comme utile de pouvoir par défaut les deux avec une seule déclaration. Donc, si vous par défaut operator<=>, il a été décidé que vous vouliez également par défaut operator==(à moins que vous ne le définissiez plus tard ou que vous l'ayez défini plus tôt).

Quant à savoir pourquoi cette décision a été prise , le raisonnement de base est le suivant. Considérez std::string. L'ordre de deux chaînes est lexicographique; chaque caractère a sa valeur entière comparée à chaque caractère de l'autre chaîne. La première inégalité résulte du résultat de la commande.

Cependant, le test d'égalité des chaînes a un court-circuit. Si les deux chaînes ne sont pas de longueur égale, alors il n'y a aucun intérêt à faire une comparaison par caractère; ils ne sont pas égaux. Donc, si quelqu'un fait des tests d'égalité, vous ne voulez pas le faire en long et en large si vous pouvez le court-circuiter.

Il s'avère que de nombreux types nécessitant un ordre défini par l'utilisateur offriront également un mécanisme de court-circuit pour les tests d'égalité. Pour empêcher les gens d'implémenter uniquement operator<=>et de gâcher les performances potentielles, nous forçons efficacement chacun à faire les deux.

Nicol Bolas
la source
5
Ceci est une bien meilleure explication que la réponse acceptée
note
17

Les autres réponses expliquent très bien pourquoi la langue est comme ça. Je voulais juste ajouter que dans le cas où ce n'est pas évident, il est bien sûr possible d'avoir un utilisateur fourni operator<=>avec un défaut operator==. Vous avez juste besoin d'écrire explicitement la valeur par défaut operator==:

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