Aucun opérateur == trouvé lors de la comparaison de structures en C ++

96

En comparant deux instances de la structure suivante, je reçois une erreur:

struct MyStruct1 {
    MyStruct1(const MyStruct2 &_my_struct_2, const int _an_int = -1) :
        my_struct_2(_my_struct_2),
        an_int(_an_int)
    {}

    std::string toString() const;

    MyStruct2 my_struct_2;
    int an_int;
};

L'erreur est:

erreur C2678: binaire '==': aucun opérateur trouvé qui prend un opérande de gauche de type 'myproj :: MyStruct1' (ou il n'y a pas de conversion acceptable)

Pourquoi?

Jonathan
la source

Réponses:

126

En C ++, les structs n'ont pas d'opérateur de comparaison généré par défaut. Vous devez écrire le vôtre:

bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return /* your comparison code goes here */
}
Anthony Williams
la source
21
@Jonathan: Pourquoi le C ++ saurait-il comment vous voulez comparer vos structs pour l'égalité? Et si vous voulez la manière simple, il y a toujours memcmptellement longtemps que vos structures ne contiennent pas de pointeur.
Xeo
12
@Xeo: memcmpéchoue avec les membres non-POD (comme std::string) et les structures rembourrées.
fredoverflow
16
@Jonathan Les langages "modernes" que je connais fournissent un ==opérateur --- avec une sémantique qui n'est presque jamais ce que l'on veut. (Et ils ne fournissent pas un moyen de le remplacer, vous finissez donc par devoir utiliser une fonction membre). Les langages «modernes» que je connais ne fournissent pas non plus de sémantique de valeur, vous êtes donc obligé d'utiliser des pointeurs, même lorsqu'ils ne sont pas appropriés.
James Kanze
4
Les cas @Jonathan varient certainement, même au sein d'un programme donné. Pour les objets entité, la solution fournie par Java fonctionne très bien (et bien sûr, vous pouvez faire exactement la même chose en C ++ --- c'est même C ++ idiomatique pour les objets entité). La question est de savoir quoi faire des objets de valeur. C ++ fournit une valeur par défaut operator=(même s'il fait souvent la mauvaise chose), pour des raisons de compatibilité C. operator==Cependant, la compatibilité C ne nécessite pas de fichier . Globalement, je préfère ce que fait C ++ à ce que fait Java. (Je ne connais pas C #, alors peut-être que c'est mieux.)
James Kanze
9
Au moins ça devrait être possible = default!
user362515
94

C ++ 20 a introduit des comparaisons par défaut, alias le "vaisseau spatial"operator<=> , qui vous permet de demander des opérateurs </ <=/ ==/ !=/ >=/ et / ou générés par le compilateur >avec l'implémentation évidente / naïve (?) ...

auto operator<=>(const MyClass&) const = default;

... mais vous pouvez personnaliser cela pour des situations plus compliquées (voir ci-dessous). Voir ici la proposition de langue, qui contient des justifications et des discussions. Cette réponse reste pertinente pour C ++ 17 et versions antérieures, et pour savoir quand vous devez personnaliser l'implémentation de operator<=>....

Cela peut sembler un peu inutile de C ++ de ne pas l'avoir déjà standardisé plus tôt, mais les structures / classes ont souvent des données membres à exclure de la comparaison (par exemple, les compteurs, les résultats mis en cache, la capacité du conteneur, le code de réussite de la dernière opération / erreur, les curseurs), comme ainsi que des décisions à prendre sur une myriade de choses, y compris mais sans s'y limiter:

  • les champs à comparer en premier, par exemple la comparaison d'un intmembre particulier peut éliminer très rapidement 99% des objets inégaux, tandis qu'un map<string,string>membre peut souvent avoir des entrées identiques et être relativement coûteux à comparer - si les valeurs sont chargées à l'exécution, le programmeur peut avoir des informations sur le le compilateur ne peut pas
  • dans la comparaison de chaînes: respect de la casse, équivalence des espaces et des séparateurs, conventions d'échappement ...
  • précision lors de la comparaison des flottants / doubles
  • si les valeurs à virgule flottante NaN doivent être considérées comme égales
  • comparer des pointeurs ou pointés vers des données (et dans ce dernier cas, comment savoir si les pointeurs sont vers des tableaux et combien d'objets / octets à comparer)
  • si l'ordre est important lors de la comparaison de conteneurs non triés (par exemple vector, list), et si oui, s'il est correct de les trier sur place avant de comparer ou d'utiliser de la mémoire supplémentaire pour trier les temporaires chaque fois qu'une comparaison est effectuée
  • combien d'éléments du tableau contiennent actuellement des valeurs valides à comparer (y a-t-il une taille quelque part ou une sentinelle?)
  • quel membre de a unioncomparer
  • normalisation: par exemple, les types de date peuvent autoriser un jour du mois ou un mois de l'année hors de la plage, ou un objet rationnel / fraction peut avoir 6 / 8ème tandis qu'un autre en a 3/4, ce qui, pour des raisons de performance, corrige paresseusement avec une étape de normalisation séparée; vous devrez peut-être décider de déclencher une normalisation avant la comparaison
  • que faire lorsque les pointeurs faibles ne sont pas valides
  • comment gérer les membres et les bases qui ne s'implémentent pas operator==eux-mêmes (mais qui pourraient avoir compare()ou operator<ou str()ou getters ...)
  • quels verrous doivent être pris lors de la lecture / comparaison des données que d'autres threads peuvent vouloir mettre à jour

Donc, c'est plutôt bien d'avoir une erreur jusqu'à ce que vous ayez explicitement réfléchi à ce que la comparaison devrait signifier pour votre structure spécifique, plutôt que de la laisser compiler mais pas de vous donner un résultat significatif au moment de l'exécution .

Cela dit, ce serait bien si C ++ vous laisse dire bool operator==() const = default;quand vous avez décidé qu'un ==test membre par membre "naïf" était acceptable. Pareil pour !=. Compte tenu de plusieurs membres / bases, « default » <, <=, >, et >=mises en œuvre semblent sans espoir que - en cascade sur la base de l' ordre de de déclaration possible , mais très peu de chances d'être ce qui a voulu, compte tenu des impératifs contradictoires pour la commande des membres (bases étant nécessairement avant que les membres, le regroupement par accessibilité, construction / destruction avant utilisation dépendante). Pour être plus largement utile, C ++ aurait besoin d'un nouveau système d'annotation de membre de données / base pour guider les choix - ce serait une bonne chose à avoir dans le Standard, idéalement couplé à la génération de code défini par l'utilisateur basé sur AST ... il'

Implémentation typique des opérateurs d'égalité

Une mise en œuvre plausible

Il est probable qu'une mise en œuvre raisonnable et efficace serait:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.my_struct2 == rhs.my_struct2 &&
           lhs.an_int     == rhs.an_int;
}

Notez que cela nécessite un operator==pour MyStruct2aussi.

Les implications de cette implémentation et des alternatives sont discutées sous la rubrique Discussion des spécificités de votre MyStruct1 ci-dessous.

Une approche cohérente de ==, <,> <= etc

Il est facile de tirer parti std::tupledes opérateurs de comparaison pour comparer vos propres instances de classe - utilisez simplement std::tiepour créer des tuples de références aux champs dans l'ordre de comparaison souhaité. Généraliser mon exemple à partir d' ici :

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) ==
           std::tie(rhs.my_struct2, rhs.an_int);
}

inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) <
           std::tie(rhs.my_struct2, rhs.an_int);
}

// ...etc...

Lorsque vous "possédez" (c'est-à-dire que vous pouvez éditer un facteur avec les bibliothèques d'entreprise et tierces) la classe que vous voulez comparer, et en particulier avec la préparation de C ++ 14 à déduire le type de retour de fonction de l' returninstruction, il est souvent plus agréable d'ajouter un " liez "la fonction membre à la classe que vous voulez pouvoir comparer:

auto tie() const { return std::tie(my_struct1, an_int); }

Ensuite, les comparaisons ci-dessus se simplifient en:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.tie() == rhs.tie();
}

Si vous voulez un ensemble plus complet d'opérateurs de comparaison, je suggère des opérateurs de boost (recherche de less_than_comparable). Si cela ne convient pas pour une raison quelconque, vous pouvez ou non aimer l'idée de macros de support (en ligne) :

#define TIED_OP(STRUCT, OP, GET_FIELDS) \
    inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
    { \
        return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
    }

#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
    TIED_OP(STRUCT, ==, GET_FIELDS) \
    TIED_OP(STRUCT, !=, GET_FIELDS) \
    TIED_OP(STRUCT, <, GET_FIELDS) \
    TIED_OP(STRUCT, <=, GET_FIELDS) \
    TIED_OP(STRUCT, >=, GET_FIELDS) \
    TIED_OP(STRUCT, >, GET_FIELDS)

... qui peut ensuite être utilisé à la ...

#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)

(Version C ++ 14 membres-tie ici )

Discussion sur les spécificités de votre MyStruct1

Il y a des implications au choix de fournir un indépendant plutôt qu'un membre operator==()...

Mise en œuvre autonome

Vous avez une décision intéressante à prendre. Comme votre classe peut être implicitement construite à partir de a MyStruct2, une fonction autonome / non membre bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)prendrait en charge ...

my_MyStruct2 == my_MyStruct1

... en créant d' abord temporaire à MyStruct1partir my_myStruct2, puis en faisant la comparaison. Cela laisserait définitivement MyStruct1::an_intla valeur de paramètre par défaut du constructeur de -1. Selon que vous incluez an_intcomparaison dans la mise en œuvre de votre operator==, un MyStruct1pourrait ou non être égaux à un MyStruct2qui se compare égal au MyStruct1de my_struct_2membre! En outre, la création d'un temporaire MyStruct1peut être une opération très inefficace, car elle implique de copier le my_struct2membre existant dans un temporaire, uniquement pour le jeter après la comparaison. (Bien sûr, vous pouvez empêcher cette construction implicite de MyStruct1s pour la comparaison en créant ce constructeur explicitou en supprimant la valeur par défaut de an_int.)

Implémentation des membres

Si vous souhaitez éviter la construction implicite de a à MyStruct1partir de a MyStruct2, faites de l'opérateur de comparaison une fonction membre:

struct MyStruct1
{
    ...
    bool operator==(const MyStruct1& rhs) const
    {
        return tie() == rhs.tie(); // or another approach as above
    }
};

Notez que le constmot-clé - uniquement nécessaire pour l'implémentation du membre - informe le compilateur que la comparaison d'objets ne les modifie pas, donc peut être autorisée sur les constobjets.

Comparaison des représentations visibles

Parfois, le moyen le plus simple d'obtenir le type de comparaison que vous souhaitez peut être ...

    return lhs.to_string() == rhs.to_string();

... ce qui est souvent très cher aussi - ceux qui ont été stringcréés douloureusement juste pour être jetés! Pour les types avec des valeurs à virgule flottante, la comparaison des représentations visibles signifie que le nombre de chiffres affichés détermine la tolérance dans laquelle des valeurs presque égales sont traitées comme égales lors de la comparaison.

Tony Delroy
la source
Eh bien, en fait, pour les opérateurs de comparaison <,>, <=,> = il ne devrait être nécessaire que d'implémenter <. Le reste suit, et il n'y a aucun moyen significatif de les implémenter, ce qui signifie autre chose que l'implémentation qui peut être générée automatiquement. Il est étrange que vous deviez tous les mettre en œuvre vous-même.
André
@ André: plus souvent écrit manuellement int cmp(x, y)ou comparefonction renvoyant une valeur négative pour x < y, 0 pour l' égalité et une valeur positive pour x > yest utilisée comme base pour <, >, <=, >=, ==et !=; il est très facile d'utiliser le CRTP pour injecter tous ces opérateurs dans une classe. Je suis sûr que j'ai publié la mise en œuvre dans une ancienne réponse, mais je n'ai pas pu la trouver rapidement.
Tony Delroy
@TonyD Bien sûr, vous pouvez le faire, mais c'est tout aussi simple à mettre en œuvre >, <=et >=en termes de <. Vous pouvez également mettre en œuvre ==et de !=cette façon, mais ce ne serait généralement pas une mise en œuvre très efficace, je suppose. Ce serait bien si aucun CRTP ou autre astuce ne serait nécessaire pour tout cela, mais la norme exigerait simplement la génération automatique de ces opérateurs si elle n'est pas explicitement définie par l'utilisateur et <est définie.
André
@ André: c'est parce que ==et !=peut ne pas être efficacement exprimé en utilisant <que l'utilisation de comparer pour tout est courante. « Ce serait bien si aucun CRTP ou d' autres tours seraient nécessaires » - peut - être, mais CRTP peut facilement être utilisé pour générer beaucoup d'autres opérateurs (par exemple au niveau du bit |, &, ^de |=, &=et ^=, + - * / %de leurs formes d'affectation, binaire -de la négation unaire et +) - tellement de variations potentiellement utiles sur ce thème que le simple fait de fournir une fonctionnalité de langage pour une tranche assez arbitraire de ce thème n'est pas particulièrement élégant.
Tony Delroy
Pourriez-vous ajouter à Une implémentation plausible une version qui utilise std::tiepour faire la comparaison de plusieurs membres?
NathanOliver
17

Vous devez définir explicitement operator ==pour MyStruct1.

struct MyStruct1 {
  bool operator == (const MyStruct1 &rhs) const
  { /* your logic for comparision between "*this" and "rhs" */ }
};

Désormais, la comparaison == est légale pour 2 de ces objets.

iammilind
la source
11

A partir de C ++ 20, il devrait être possible d'ajouter un ensemble complet d'opérateurs de comparaison par défaut ( ==, <=, etc.) à une classe en déclarant un défaut opérateur de comparaison à trois voies (opérateur « vaisseau spatial »), comme ceci:

struct Point {
    int x;
    int y;
    auto operator<=>(const Point&) const = default;
};

Avec un compilateur C ++ 20 conforme, l'ajout de cette ligne à MyStruct1 et MyStruct2 peut être suffisant pour permettre des comparaisons d'égalité, en supposant que la définition de MyStruct2 est compatible.

Joe Lee
la source
2

La comparaison ne fonctionne pas sur les structures en C ou C ++. Comparez plutôt par champs.

Rafe Kettler
la source
2

Par défaut, les structures n'ont pas d' ==opérateur. Vous devrez écrire votre propre implémentation:

bool MyStruct1::operator==(const MyStruct1 &other) const {
    ...  // Compare the values, and return a bool result.
  }
Jonathan
la source
0

Hors de la boîte, l'opérateur == ne fonctionne que pour les primitives. Pour que votre code fonctionne, vous devez surcharger l'opérateur == pour votre structure.

Babak Naffas
la source
0

Parce que vous n'avez pas écrit d'opérateur de comparaison pour votre structure. Le compilateur ne le génère pas pour vous, donc si vous voulez une comparaison, vous devez l'écrire vous-même.


la source