web-dev-qa-db-fra.com

Aucun opérateur == trouvé lors de la comparaison de 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::string toString() const;

    MyStruct2 my_struct_2;
    int an_int;
};

L'erreur est:

erreur C2678: binaire '==': aucun opérateur trouvé ne prend d'opérande gauche du type 'myproj :: MyStruct1' (ou aucune conversion acceptable)

Pourquoi?

86
Jonathan

En C++, structs n'a 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 */
}
116
Anthony Williams

Comme d'autres personnes l'ont dit, vous devez implémenter vous-même une fonction de comparaison.

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

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

  • les champs à comparer en premier, par ex. comparer un membre int particulier peut éliminer très rapidement 99% des objets inégaux, alors qu'un membre map<string,string> peut souvent avoir des entrées identiques et être relativement coûteux à comparer - si les valeurs sont chargées à l'exécution, programmeur peut avoir des idées le compilateur ne peut pas éventuellement
  • en comparant les chaînes: sensibilité à la casse, équivalence d'espaces et de séparateurs, conventions d'échappement ...
  • précision lors de la comparaison des floats/doubles
  • si les valeurs de la virgule flottante NaN doivent être considérées comme égales
  • comparer des pointeurs ou des données pointées (et si ces dernières, comment savoir si les pointeurs sont dirigés vers des tableaux et combien d'objets/octets nécessitant une comparaison)
  • si l'ordre est important lors de la comparaison de conteneurs non triés (par exemple, vector, list) et, dans l'affirmative, 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 comparaison est fait
  • combien d'éléments de tableau contiennent actuellement des valeurs valides qui doivent être comparées (y a-t-il une taille quelque part ou une sentinelle?)
  • quel membre d'un union comparer
  • normalisation: par exemple, les types de date peuvent permettre un jour ou un mois en dehors de la plage, ou un objet rationnel/fraction peut avoir 6/8e alors qu'un autre a 3/4ers, corrigé pour des raisons de performance 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 des getters ...)
  • quels verrous doivent être pris lors de la lecture/comparaison de données que d'autres threads peuvent vouloir mettre à jour

Donc, c'est un peu agréable 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 le laisser compiler mais ne pas vous donner un résultat significatif au moment de l'exécution .

Cela dit, il serait bon que C++ vous laisse dire bool operator==() const = default; lorsque vous auriez décidé de procéder à un test "naïf" membre par membre == était ok. Même chose pour !=. Avec plusieurs membres/bases, les implémentations "par défaut" <, <=, > Et >= Semblent sans espoir - cascadées sur la base de l'ordre de déclaration possible mais très peu probable d'être ce que l'on souhaite, étant donné les impératifs contradictoires en matière de commande des membres (les bases étant nécessairement avant les membres, le regroupement par accessibilité, la construction/destruction avant une utilisation dépendante). Pour être plus utile, le C++ aurait besoin d’un nouveau système d’annotation base de données/données pour guider les choix - ce serait un atout majeur dans la norme mais, idéalement, associé à une génération de code définie par l’utilisateur basée sur AST ... Je pense ça va arriver un jour.

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

Une implémentation plausible

C'est probable qu'une implémentation 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 ceci nécessite également un operator== Pour MyStruct2.

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 des opérateurs de comparaison de std::Tuple Pour comparer vos propres instances de classe. Il vous suffit d'utiliser std::tie Pour créer des n-uplets de références à des champs dans l'ordre de comparaison souhaité. Généraliser mon exemple de 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 souhaitez comparer, et en particulier avec la capacité de C++ 14 à déduire le type de retour de fonction de l'instruction return, c'est Il est souvent plus agréable d’ajouter une fonction membre "tie" à la classe que vous voulez pouvoir comparer:

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

Ensuite, les comparaisons ci-dessus se simplifient pour:

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 opérateurs de boost (recherchez less_than_comparable). Si cela ne vous convient pas pour une raison quelconque, vous pouvez ou non aimer l’idée des 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 alors être utilisé à la ...

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

(Version cravate de membre C++ 14 ici )

Discussion des spécificités de votre MyStruct1

Il y a des implications pour le choix de fournir un nom indépendant par rapport à un membre operator==()...

Implémentation autonome

Vous avez une décision intéressante à prendre. Comme votre classe peut être construite de manière implicite à partir de 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 un MyStruct1 temporaire à partir de my_myStruct2, puis en effectuant la comparaison. Ceci laisserait définitivement MyStruct1::an_int Défini sur la valeur de paramètre par défaut du constructeur de -1. Selon que vous incluez la comparaison an_int Dans la mise en œuvre de votre operator==, Un MyStruct1 Peut être égal ou non à un MyStruct2 Qui se compare lui-même à le membre MyStruct1my_struct_2! De plus, créer un MyStruct1 Temporaire peut être une opération très inefficace, car cela implique de copier le membre my_struct2 Existant dans un temporaire, uniquement pour le jeter après la comparaison. (Bien entendu, vous pouvez éviter cette construction implicite de MyStruct1 À des fins de comparaison en rendant ce constructeur explicit ou en supprimant la valeur par défaut de an_int.)

Implémentation du membre

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 (uniquement nécessaire pour l'implémentation de membre) indique au compilateur que la comparaison d'objets ne les modifie pas et peut donc être autorisée sur les objets 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_string() == rhs.to_string();

... qui coûte souvent très cher - ces strings sont cruellement créés 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 à l'intérieur de laquelle les valeurs presque égales sont traitées comme étant égales lors de la comparaison.

81
Tony Delroy

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

14
iammilind

La comparaison ne fonctionne pas sur les structures en C ou C++. Comparez par champs à la place.

2
Rafe Kettler

À 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 opérateur de comparaison à trois voies par défaut (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 suffire à permettre des comparaisons d'égalité, en supposant que la définition de MyStruct2 soit compatible.

2
Joe Lee

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

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

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 == de votre structure.

0
Babak Naffas

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.

0
user500944