Pourquoi l'inégalité est-elle testée comme (! (A == b)) dans beaucoup de code de bibliothèque standard C ++?

142

Il s'agit du code du code de la bibliothèque standard C ++ remove. Pourquoi les inégalités sont-elles testées au if (!(*first == val))lieu de if (*first != val)?

 template <class ForwardIterator, class T>
      ForwardIterator remove (ForwardIterator first, ForwardIterator last, const T& val)
 {
     ForwardIterator result = first;
     while (first!=last) {
         if (!(*first == val)) {
             *result = *first;
             ++result;
         }
         ++first;
     }
     return result;
 }
Ahmed Nawar
la source
2
@BeyelerStudios a probablement raison. C'est également courant lors de la mise en œuvre operator!=. Utilisez simplement l' operator==implémentation:bool operator!=(const Foo& other) { return !(*this == other); }
simon
1
en fait, je corrige ma déclaration: la mention des références supprime tous les éléments qui sont égaux à la valeur, donc operator==on s'attend à ce qu'ils soient utilisés ici ...
BeyelerStudios
Oh, et il devrait également y avoir un constdans l'exemple de mon commentaire précédent, mais vous comprenez. (Trop tard pour le modifier)
simon
La raison en est liée à une autre question (à laquelle on peut essentiellement répondre par "Non, pas nécessairement"), et au concept d'être EqualityComparableque Hurkyl a mentionné dans sa réponse .
Marco13

Réponses:

144

Parce que cela signifie que la seule exigence sur T est d'implémenter un operator==. Vous pourriez demander à T d'avoir un, operator!=mais l'idée générale ici est que vous devriez mettre le moins de charge possible sur l'utilisateur du modèle et que d'autres modèles en ont besoin operator==.

Tom Tanner
la source
13
template <class T> opérateur booléen en ligne! = <T a, T b> {return! (a == b); }
Joshua
8
Y aurait-il un scénario où un compilateur ne pourrait pas échanger toutes les instances de =! à! (==)? Pourquoi ne serait-ce pas déjà l'action par défaut du compilateur?
Aidan Gomez
20
@AidanGomez Pour le meilleur ou pour le pire, vous pouvez surcharger les opérateurs pour qu'ils fassent ce que vous voulez. Cela n'a pas besoin d'être logique ou cohérent.
Neil Kirk
10
x != yn'est pas défini comme étant identique à !(x == y). Que faire si ces opérateurs renvoient l'arborescence d'analyse d'un DSL intégré?
Brice M. Dempsey
7
@Joshua Cela casse mal si vous essayez d'utiliser SFINAE pour détecter si !=est pris en charge (retournerait incorrectement true - même s'il operator==n'est pas pris en charge!). Je crains également que certaines utilisations de !=deviennent ambiguës.
36

La plupart des fonctions de STL fonctionnent uniquement avec operator<ou operator==. Cela oblige l'utilisateur à implémenter uniquement ces deux opérateurs (ou parfois au moins l'un d'entre eux). Par exemple std::setutilise operator<(plus précisément std::lessqui invoque operator<par défaut) et non operator>pour gérer la commande. Le removemodèle dans votre exemple est un cas similaire - il utilise uniquement operator==et non operator!=donc le operator!=n'a pas besoin d'être défini.

Lukáš Bednařík
la source
2
Les fonctions n'utilisent pas operator<directement mais utilisent à la place std::less, ce qui à son tour est par défaut operator<.
Christian Hackl
1
En fait, il semble que les fonctions d'algorithme standard, contrairement à par exemple std::set, utilisent effectivement operator<directement. Strange ...
Christian Hackl
1
Ces fonctions n'utilisent pas std::equal_to, elles utilisent operator==comme indiqué dans la question. La situation avec std::lessest similaire. Eh bien, ce std::setn'est peut - être pas le meilleur exemple.
Lukáš Bednařík
2
@ChristianHackl, std::equal_toet std::lesssont utilisés comme paramètres de modèle par défaut où le comparateur est pris comme paramètre. operator==et operator<sont utilisés directement lorsque le type est requis pour satisfaire respectivement une égalité comparable et un ordre faible strict, par exemple des itérateurs et des itérateurs à accès aléatoire.
Jan Hudec
28

Il s'agit du code de la bibliothèque standard C ++ remove code.

Faux. Ce n'est pas leremove code de la bibliothèque standard C ++ . C'est une implémentation interne possible de la removefonction de bibliothèque standard C ++ . La norme C ++ ne prescrit pas de code réel; il prescibe les prototypes de fonction et les comportements requis.

En d'autres termes: du point de vue du langage strict, le code que vous voyez n'existe pas . Cela peut provenir d'un fichier d'en-tête fourni avec l'implémentation de la bibliothèque standard de votre compilateur. Notez que la norme C ++ n'exige même pas que ces fichiers d'en- tête existent. Les fichiers sont juste un moyen pratique pour les implémenteurs de compilateurs de répondre aux exigences d'une ligne comme #include <algorithm>(c'est-à-dire rendre std::removeet d'autres fonctions disponibles).

Pourquoi les inégalités sont-elles testées au if (!(*first == val))lieu de if (*first != val)?

Parce que seul operator==est requis par la fonction.

En ce qui concerne la surcharge d'opérateurs pour les types personnalisés, le langage vous permet de faire toutes sortes de choses étranges. Vous pouvez très bien créer une classe qui a un fichier surchargé operator==mais pas surchargé operator!=. Ou pire encore: vous pourriez surcharger operator!=mais lui faire faire des choses totalement indépendantes.

Prenons cet exemple:

#include <algorithm>
#include <vector>

struct Example
{
    int i;

    Example() : i(0) {}

    bool operator==(Example const& other) const
    {
        return i == other.i;
    }

    bool operator!=(Example const& other) const
    {
        return i == 5; // weird, but nothing stops you
                       // from doing so
    }

};

int main()
{
  std::vector<Example> v(10);
  // ...
  auto it = std::remove(v.begin(), v.end(), Example());
  // ...
}

Si std::removeutilisé operator!=, le résultat serait tout à fait différent.

Christian Hackl
la source
1
Une autre chose à considérer est qu'il peut être possible pour les deux a==bet a!=bde renvoyer false. S'il n'est pas toujours clair si une telle situation serait plus significativement considérée comme "égale" ou "non-égale", une fonction qui définit l'égalité uniquement en fonction de l'opérateur "==" doit les considérer comme "non égales" ", quel que soit le comportement qui aurait le plus de sens [si j'avais mes druthers, tous les types seraient censés faire en sorte que les opérateurs" == "et"! = "produisant des valeurs booléennes se comportent de manière cohérente, mais le fait que IEEE-754 impose une égalité rompue les opérateurs rendraient cette attente difficile].
supercat du
18
"D'un point de vue linguistique strict, le code que vous voyez n'existe pas." - lorsqu'un point de vue dit que quelque chose n'existe pas, mais que vous regardez réellement cette chose, alors le point de vue est faux. En fait, le standard ne dit pas que le code n'existe pas, il ne dit simplement pas qu'il existe :-)
Steve Jessop
@SteveJessop: Vous pouvez remplacer l'expression "d'un point de vue strict du langage" par quelque chose comme "strictement, au niveau du langage". Le fait est que le code affiché par l'OP ne relève pas de la norme linguistique.
Christian Hackl
2
@supercat: IEEE-754 crée ==et !=se comporte de manière cohérente, même si j'ai toujours pensé que les six relations devraient être évaluées à falsequand au moins un opérande est NaN.
Ben Voigt
@BenVoigt: Ah, c'est vrai. Cela fait en sorte que les deux opérateurs se comportent de la même manière cassée de sorte qu'ils soient cohérents l'un avec l'autre, mais parviennent toujours à violer tous les autres axiomes normaux associés à l'équivalence (par exemple, ils ne respectent ni a == a, ni la garantie que les opérations effectuées à valeurs égales donnera des résultats égaux).
supercat du
15

Quelques bonnes réponses ici. Je voulais juste ajouter une petite note.

Comme toutes les bonnes bibliothèques, la bibliothèque standard est conçue avec (au moins) deux principes très importants à l'esprit:

  1. Mettez le moins de responsabilité possible sur les utilisateurs de votre bibliothèque avec lesquels vous pouvez vous en tirer. Une partie de cela a à voir avec leur donner le moins de travail à faire lors de l'utilisation de votre interface. (comme définir le moins d'opérateurs possible). L'autre partie est de ne pas les surprendre ou de les obliger à vérifier les codes d'erreur (alors gardez les interfaces cohérentes et jetez des exceptions <stdexcept>lorsque les choses tournent mal).

  2. Éliminez toute redondance logique . Toutes les comparaisons peuvent être déduites simplement de operator<, alors pourquoi exiger que les utilisateurs définissent les autres? par exemple:

    (a> b) équivaut à (b <a)

    (a> = b) équivaut à! (a <b)

    (a == b) équivaut à! ((a <b) || (b <a))

    etc.

    Bien sûr sur cette note, on pourrait se demander pourquoi unordered_maprequiert operator==(au moins par défaut) plutôt que operator<. La réponse est que dans une table de hachage, la seule comparaison dont nous ayons jamais besoin est celle pour l'égalité. Il est donc plus cohérent logiquement (c'est-à-dire plus logique pour l'utilisateur de la bibliothèque) de lui demander de définir un opérateur d'égalité. Exiger un operator<serait déroutant car il n'est pas immédiatement évident de savoir pourquoi vous en auriez besoin.

Richard Hodges
la source
10
Concernant votre deuxième point: il existe des types qui peuvent logiquement être comparés pour l'égalité même s'il n'y a pas d'ordre logique, ou pour lesquels l'établissement d'un ordre serait très artificiel. Par exemple, le rouge est rouge et le rouge n'est pas vert, mais le rouge est-il intrinsèquement inférieur au vert?
Christian Hackl
1
Entièrement d'accord. On ne stockerait pas ces articles dans un conteneur ordonné car il n'y a pas d'ordre logique. Ils pourraient plus convenablement être stockés dans un conteneur non ordonné qui nécessite operator==(et hash).
Richard Hodges
3. Surchargez les opérateurs les moins standard possibles. C'est une autre raison pour laquelle ils ont mis en œuvre !(a==b). Parce qu'une surcharge non réfléchie des opérateurs peut facilement causer un désordre complet du programme C ++ (en plus, rendre le programmeur fou car le débogage de son code peut devenir une mission impossible, car trouver le coupable d'un bogue particulier ressemble à une odyssée).
syntaxerror
!((a < b) || (b < a))utilise un opérateur booléen de moins, donc c'est probablement plus rapide
Filip Haglund
1
En réalité, ils ne le font pas. En langage assembleur, toutes les comparaisons sont implémentées sous forme de soustraction suivie d'un test de report et de zéro bit dans le registre des indicateurs. Tout le reste n'est que du sucre syntaxique.
Richard Hodges
8

Le EqualityComparableconcept ne demande qu'à operator==être défini.

Par conséquent, toute fonction qui prétend travailler avec des types satisfaisants EqualityComparable ne peut pas s'appuyer sur l'existence de operator!=for objets de ce type. (sauf s'il existe des exigences supplémentaires qui impliquent l'existence de operator!=).


la source
1

L'approche la plus prometteuse consiste à trouver une méthode pour déterminer si l'opérateur == peut être appelé pour un type particulier, puis à ne le prendre en charge que lorsqu'il est disponible; dans d'autres situations, une exception serait levée. Cependant, à ce jour, il n'existe aucun moyen connu de détecter si une expression d'opérateur arbitraire f == g est correctement définie. La meilleure solution connue a les qualités indésirables suivantes:

  • Échec lors de la compilation pour les objets où l'opérateur == n'est pas accessible (par exemple, parce qu'il est privé).
  • Échoue à la compilation si l'appel de l'opérateur == est ambigu.
  • Semble être correct si la déclaration de l'opérateur == est correcte, même si l'opérateur == ne peut pas être compilé.

De Boost FAQ: source

Sachant que la ==mise en œuvre est un fardeau , vous ne voulez jamais créer de charge supplémentaire en exigeant également la !=mise en œuvre.

Pour moi personnellement, il s'agit de SOLID (conception orientée objet) Partie L - principe de substitution de Liskov: «les objets d'un programme doivent être remplaçables par des instances de leurs sous-types sans altérer l'exactitude de ce programme.». Dans ce cas c'est l'opérateur ! = Que je peux remplacer par == et l' inverse booléen en logique booléenne.

Margus
la source