Pas d’opérateur == trouvé en comparant les structures en C ++

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

struct MyStruct1 { Position(const MyStruct2 &_my_struct_2, const int _an_int = -1) : my_struct_2(_my_struct_2), an_int(_an_int) {} std::ssortingng toSsortingng() const; MyStruct2 my_struct_2; int an_int; }; 

L’erreur est:

erreur C2678: binary ‘==’: aucun opérateur trouvé prenant un opérande gauche de type ‘myproj :: MyStruct1’ (ou il n’y a pas de conversion acceptable)

Pourquoi?

En C ++, les struct 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 */ } 

Comme d’autres l’ont dit, vous devez mettre en place une fonction de comparaison vous-même.

Il y a un moyen proposé de demander au compilateur de générer l’implémentation évidente / naïve (?): Voir ici .

Il peut sembler un peu inutile de C ++ de ne pas l’avoir déjà normalisé, mais souvent les structs / classes ont des membres de données à exclure de la comparaison (p. Ex. Compteurs, résultats mis en cache, capacité du conteneur, code succès / erreur de la dernière opération) en tant que décisions à prendre à propos d’une myriade de choses, y compris, mais sans s’y limiter:

  • Les champs à comparer en premier, par exemple en comparant un membre int particulier, peuvent éliminer 99% des objects inégaux très rapidement, alors qu’un membre map peut souvent avoir des entrées identiques et être relativement coûteux à comparer. , le programmeur peut avoir des idées que le compilateur ne peut pas
  • en comparant les chaînes: sensibilité à la casse, équivalence des espaces et des séparateurs, conventions d’échappement …
  • précision lors de la comparaison flotteurs / doubles
  • si les valeurs à virgule flottante NaN doivent être considérées égales
  • comparer des pointeurs ou des données pointées (et si ce dernier, comment savoir si les pointeurs sont des tableaux et combien d’objects / octets doivent être comparés)
  • si l’ordre est important lorsque l’on compare des conteneurs non sortingés (par exemple, vector , list ), et si oui, est-il correct de les sortinger avant de comparer ou d’utiliser de la mémoire supplémentaire pour sortinger les données temporaires?
  • combien d’éléments de tableau contiennent actuellement des valeurs valides à comparer (existe-t-il une taille quelque part ou une sentinelle?)
  • quel membre d’un union comparer
  • normalisation: par exemple, les types de date peuvent autoriser un dépassement du jour du mois ou de l’année, ou un object rationnel / fraction peut avoir 6 / 8ème tandis qu’un autre a 3 / 4ers, ce qui pour des raisons de performances ils corrigent 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 n’implémentent pas operator== eux-mêmes (mais peuvent 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, il est agréable d'avoir une erreur jusqu'à ce que vous ayez pensé explicitement à ce que la comparaison devrait signifier pour votre structure spécifique, plutôt que de la laisser comstackr sans vous donner un résultat significatif au moment de l'exécution .

Cela dit, ce serait bien si C ++ vous permettait de dire l' bool operator==() const = default; quand vous aviez décidé qu'un test "naïf" membre par membre était correct. Même chose pour != . Étant donné les multiples membres / bases, les implémentations "default" < , <= , > et >= semblent sans espoir bien que - en cascade d’ordre de déclaration soit possible mais très improbable compte tenu des impératifs contradictoires nécessairement avant les membres, regroupés par accessibilité, construction / destruction avant utilisation dépendante). Pour être plus utile, le C ++ nécessiterait un nouveau système d'annotation membre / firebase database pour guider les choix - ce serait une bonne chose dans la norme, idéalement couplé à la génération de code définie par l'utilisateur basée sur AST ... ça va arriver un jour.

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

Une mise en œuvre plausible

Une implémentation raisonnable et efficace serait probablement:

 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 également un operator== pour MyStruct2 .

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

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

Il est facile d'exploiter les opérateurs de comparaison std::tuple pour comparer vos propres instances de classe - utilisez simplement std::tie pour créer des tuples de références à des champs dans l'ordre de comparaison souhaité. Générer mon exemple 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.-à-d. Pouvez éditer un facteur avec les librairies 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'instruction return , "associer" la fonction membre à la classe que vous souhaitez pouvoir comparer:

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

Ensuite, les comparaisons ci-dessus simplifient:

 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 (recherchez less_than_comparable ). Si cela ne convient pas pour une raison quelconque, vous pouvez aimer ou non 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é a la ...

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

(C ++ 14 version membre-cravate ici )

Discussion des spécificités de votre MyStruct1

Il y a des implications dans le choix de fournir un operator==() autonome ou membre operator==() ...

Implantation libre

Vous avez une décision intéressante à prendre. Comme votre classe peut être implicitement construite à partir d'un MyStruct2 , un bool operator==(const MyStruct2& lhs, const MyStruct2& rhs) charge ...

 my_MyStruct2 == my_MyStruct1 

... en créant d'abord un MyStruct1 temporaire à MyStruct1 de my_myStruct2 , puis en effectuant la comparaison. Cela laisserait définitivement MyStruct1::an_int défini sur la valeur par défaut du constructeur, à savoir -1 . Selon que vous incluez ou an_int comparaison_int dans l'implémentation de votre operator== , un MyStruct1 peut ou non être comparable à un MyStruct2 qui est lui-même my_struct_2 membre my_struct_2 ! De plus, créer un MyStruct1 temporaire peut être une opération très inefficace, car il implique de copier le membre my_struct2 existant dans un my_struct2 temporaire, puis de le jeter après la comparaison. (Bien sûr, vous pouvez empêcher cette construction implicite de MyStruct1 des MyStruct1 de comparaison en rendant ce constructeur explicit ou en supprimant la valeur par défaut pour an_int .)

Mise en place des membres

Si vous voulez éviter la construction implicite d'un MyStruct1 partir d'un 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 mot-clé const , nécessaire uniquement pour l'implémentation du membre, indique au comparateur que la comparaison d'objects ne les modifie pas. Il peut donc être autorisé sur des objects const .

Comparer les représentations visibles

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

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

... ce qui est souvent très coûteux aussi - ces ssortingng créées péniblement pour être jetées! 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 les valeurs presque égales sont traitées comme égales pendant la comparaison.

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

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

Maintenant, la comparaison == est légale pour 2 de ces objects.

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

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. } 

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.

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.