web-dev-qa-db-fra.com

Pourrais-je utiliser l'opérateur == si je n'ai implémenté que l'opérateur <?

J'ai implémenté operator< Pour un certain objet. Logiquement, si !(a < b) et !(b < a), cela signifie a == b.

Est-ce déduit automatiquement? Puis-je utiliser == Si je n'implémente que <?

51
Eyzuky

C++ ne peut pas déduire cela automatiquement pour deux raisons:

  1. Cela n'a pas de sens de comparer chaque type avec operator<, Aussi le type ne définit-il pas nécessairement un operator<.
    • Cela signifie que operator== Ne peut pas être automatiquement défini en termes de operator<
  2. operator< N'est pas nécessaire pour comparer ses arguments. Un programmeur peut définir des opérateurs pour que leurs types fassent presque tout leurs arguments.
    • Cela signifie que votre déclaration à propos de !(a < b) && !(b < a) étant équivalente à a == b Peut ne pas être nécessairement vraie, en supposant que ces opérateurs sont définis.

Si vous voulez une fonction operator== Pour vos types, définissez-en une vous-même. Ce n'est pas si dur :)

// For comparing, something like this is used
bool operator==(const MyType& lhs, const MyType& rhs)
{
    // compare (or do other things!) however you want
}

// ... though it's not the only thing you can do
//  - The return type can be customised
//  - ... as can both of the arguments
const MyType& operator==(int* lhs, const MyType* const rhs)
{
    return lhs;
}
69
user7881131

Il ne peut en déduire == de < parce que tous les types ne sont pas ordonnés, comme std::complex . Est 2 + 3i > 1 + 4i ou pas?

De plus, même dans les types normalement commandés, vous ne pouvez toujours pas déduire l'égalité de > ou <, par exemple IEEE-754 NaN

double n = std::numeric_limits<double>::quiet_NaN();

std::cout << "NaN == NaN: " << (n == n) << '\n';
std::cout << "NaN < NaN: " << (n < n) << '\n';
std::cout << "NaN > NaN: " << (n > n) << '\n';
std::cout << "NaN != NaN: " << (n != n) << '\n';

Ils vont tous retourner faux sauf le dernier

48
phuclv

Non. Cette méthode fonctionne bien sur les objets de type nombre appelés totalement ordonné . Pour tous les types d’ensembles/classes, personne ne peut garantir cette relation. Même personne ne peut garantir un operator < comparerait quelque chose.

Alors == n'est rien d'autre que ==. Vous pouvez implémenter == par < mais cela ne fonctionne pas pour tout le monde et les normes C++ ne le feront pas pour vous.

18
Shitao Zhou

C++ ne déduit pas cela automatiquement. Pour operator>, operator<= Et operator>=, Vous pouvez utiliser std::rel_ops ; cela nécessite seulement operator<. Cependant, il ne fournit pas operator== En termes de operator<. Vous pouvez le faire vous-même comme ceci:

template <class T>
bool operator==(T const& lhs, T const& rhs)
{
    return !((lhs < rhs) or (rhs < lhs));
}

Notez que: !((lhs < rhs) or (rhs < lhs)) et !(lhs < rhs) and !(rhs < lhs) sont équivalents, mathématiquement.

12
Jonas

Logiquement, si! (A <b) et! (B <a) cela signifie a == b. C++ en déduit-il automatiquement? Puis-je utiliser == si je n'ai implémenté que

Pour reprendre ce que d’autres ont dit en termes mathématiques: En supposant que vous avez un operator < Qui retourne bool et définit un ordre faible strict , et vous implémentez operator == comme renvoyant !(a < b) && !(b < a), alors cet opérateur définit une relation d'équivalence conforme à l'ordre faible donné strict. Cependant, C++ n’exige pas que operator < Définisse un ordre faible strict, ni operator == Pour définir une relation d’équivalence (bien que de nombreux algorithmes standard tels que sort puissent implicitement utiliser ces opérateurs une relation d’équivalence stricte d’ordre faible faible).

Si vous souhaitez définir tous les autres opérateurs relationnels basés sur et cohérents avec l'ordre faible strict de votre operator <, Boost.Operators peut vous faire économiser un peu de frappe.

Parce qu'il est si facile de mal utiliser un operator < Qui ne répond pas aux exigences de l'algorithme standard, par exemple. en l'utilisant accidentellement via std::sort, std::lower_bound etc., je vous recommande de définir operator < soit comme un ordre faible strict ou pas du tout. L'exemple donné par CodesInChaos est un ordre partiel, qui ne répond pas à l'exigence de "transitivité d'incomparabilité" d'un ordre faible strict. Par conséquent, je recommanderais d'appeler une telle relation sous un nom différent, par exemple. bool setLess(const MySet &, const MySet &).

Sources:

12
Arne Vogel

Comme beaucoup l'ont dit, non, vous ne pouvez pas, et non le compilateur ne devrait pas.

Cela ne signifie pas qu'il ne devrait pas être facile de partir d'un < à == et toute la myriade.

boost :: operators tente de simplifier les choses. Utilisez-le et c'est fait.

Si vous voulez le faire vous-même, il ne vous faudra qu'un peu de code pour réimplémenter ce que boost vous fournit:

namespace utility {
  namespace details {
    template<class...>using void_t=void;
    template<template<class...>class Z, class, class...Ts>
    struct can_apply:std::false_type{};
    template<template<class...>class Z, class...Ts>
    struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
  }
  template<template<class...>class Z, class...Ts>
  using can_apply = ::utility::details::can_apply<Z,void,Ts...>;
}

namespace auto_operators {
  template<class T, class U>
  using less_r = decltype( std::declval<T const&>() < std::declval<U const&>() );
  template<class T, class U>
  using can_less = ::utility::can_apply<less_r, T, U>;

  struct order_from_less {
    template<class T, class U>
    using enabled = std::enable_if_t<
      std::is_base_of<order_from_less, T>{}
      && std::is_base_of<order_from_less, U>{}
      && can_less<T, U>{},
      bool
    >;
    template<class T, class U>
    friend enabled<U,T>
    operator>(T const& lhs, U const& rhs) {
      return rhs < lhs;
    }
    template<class T, class U>
    friend enabled<U,T>
    operator<=(T const& lhs, U const& rhs) {
      return !(lhs > rhs);
    }
    template<class T, class U>
    friend enabled<T,U>
    operator>=(T const& lhs, U const& rhs) {
      return !(lhs < rhs);
    }
  };
  struct equal_from_less:order_from_less {
    template<class T, class U>
    using enabled = std::enable_if_t<
      std::is_base_of<order_from_less, T>{}
      && std::is_base_of<order_from_less, U>{}
      && can_less<T, U>{} && can_less<U,T>{},
      bool
    >;
    template<class T, class U>
    friend enabled<U,T>
    operator==(T const& lhs, U const& rhs) {
      return !(lhs < rhs) && !(rhs < lhs);
    }
    template<class T, class U>
    friend enabled<U,T>
    operator!=(T const& lhs, U const& rhs) {
      return !(lhs==rhs);
    }
  };
}

Ce qui précède ne doit être écrit qu’une seule fois, ou un équivalent obtenu à partir de #include boost.

Une fois que vous avez boosté, ou ce qui précède, c’est aussi simple que quelque chose comme:

struct foo : auto_operators::equal_from_less {
  int x;
  foo( int in ):x(in) {}
  friend bool operator<( foo const& lhs, foo const& rhs ) {
    return lhs.x < rhs.x;
  }
};

et foo a maintenant tous les opérateurs de commande et de comparaison définis.

int main() {
  foo one{1}, two{2};
  std::cout << (one < two) << "\n";
  std::cout << (one > two) << "\n";
  std::cout << (one == two) << "\n";
  std::cout << (one != two) << "\n";
  std::cout << (one <= two) << "\n";
  std::cout << (one >= two) << "\n";
  std::cout << (one == one) << "\n";
  std::cout << (one != one) << "\n";
  std::cout << (one <= one) << "\n";
  std::cout << (one >= one) << "\n";
}

Exemple en direct .

Le but de tout cela est que C++ ne suppose pas, en tant que langage, que < veux dire > et >= et == tout a du sens. Mais vous pouvez écrire une bibliothèque qui vous permet de prendre un type avec < défini, et l’ajout d’une classe de base triviale rend toutes ces opérations définies avec un coût d’exécution nul.

Le compilateur ne déduit pas == de <.

Vous pouvez vérifier cela avec un exemple simple:

#include <iostream>

struct A {
    A(int r):i{r}{}
    int i;
};

bool operator<(A const & a1, A const& a2) {
    return a1.i < a2.i;
}

int main(int argc, char* argv[]) {
    A a1{2};
    A a2{3};
    if(a1 == a2) {
        std::cout << "equals\n";
    }
    return 0;
}

GCC vous donne cette erreur:

main.cpp:20:11: error: no match for 'operator==' (operand types are 'A' and 'A')

     if(a1 == a2) {
6
nefas

Il existe des modèles définis dans le std::rel_ops espaces de noms qui définissent automatiquement les opérateurs manquants.

Il ne définit pas d'opérateur d'égalité basé sur l'opérateur moins que vous le souhaitez.

C'est quand même très utile. si vous définissez l'opérateur less et l'opérateur equal, les autres opérateurs de comparaison sont gratuits.

6
Marek R

La réponse est NON, vous avez juste besoin d'un test simple

struct MyType{
    int value;
};

bool operator < (MyType& a, MyType& b)
{
    return a.value < b.value;
}

int main(int argc, char* argv[])
{
    MyType a = {3};
    MyType b = {4};
    if (a == b)
        std::cout << "a==b" << std::endl;
    if (a < b)
        std::cout << "a < b" << std::endl;
}

g ++ 4.8.2 se plaint:

main.cpp: dans la fonction ‘int main (int, char **) ':

main.cpp: 16: 11: erreur: pas de correspondance pour ‘opérateur ==’ (les types d’opérande sont ‘MyType’ et ‘MyType’)

Mais il y a quelque chose de similaire qui fonctionne en C++, vérifiez ceci concepts c ++: comparer

ça dit:

équiv (a, b), une expression équivalente à! comp (a, b) &&! comp (b, a)

5
xiaobing

En plus d'autres réponses,

Le compilateur ne peut même pas déduire != de ==

struct MyType
{
    int value;
};

bool operator == (const MyType& a, const MyType& b)
{
    return a.value == b.value;
}

int main()
{
    MyType a = {3};
    MyType b = {4};
    if (a != b)    // (* compilation Error *) 
        std::cout << "a does not equal b" << std::endl;
}

Ce serait bien s'il y avait une option pour dire au compilateur que le reste des opérateurs rationnels s'applique à votre classe.

Comme expliqué dans certaines réponses du <utility> en-tête quelque chose qui peut fournir une telle fonctionnalité. vous devrez ajouter la ligne suivante au début du main:

using namespace std::rel_ops; 

Toutefois, comme le note JDługosz, l'utilisation de cette approche est coûteuse et générera des ambiguïtés de surcharge.

4
Shadi

Prenons l'exemple suivant:

class point{

  unsigned int x;
  unsigned int y;

  public:
    bool operator <(const point& other){
      return (x+y) < (other.x+other.y);
    }

    bool operator == (const point& other){
      return (x==other.x) && (y==other.y);
    }
}

Et puis on a:

point a{1, 2};
point b{2, 1};

! (a <b),! (b <a), mais aussi! (a == b).

3
Andrew Kashpur

Genre de.
Mais vous auriez besoin de boost :: opérateurs

Les opérateurs surchargés pour les types de classe sont généralement des groupes. Si vous pouvez écrire x + y, vous voudrez probablement aussi pouvoir écrire x + = y. Si vous pouvez écrire x <y, vous voulez aussi x> y, x> = y et x <= y. De plus, à moins que votre classe ait un comportement vraiment surprenant, certains de ces opérateurs liés peuvent être définis en termes d’autres (par exemple, x> = y <=>! (X <y)). Répliquer ce standard pour plusieurs classes est à la fois fastidieux et sujet aux erreurs. Les modèles boost/operators.hpp aident à générer des opérateurs pour vous au niveau de l'espace de noms en fonction des autres opérateurs que vous avez définis dans votre classe.

2
Foo Is Bar

La réponse est claire NON. Il n'y a pas de moyen implicite. Les classes C++ permettent de surcharger les opérateurs. Donc, votre idée que logiquement, if !(a < b) and !(b < a) cela signifie a == b. est correct. Et, vous pouvez surcharger les opérateurs comme ci-dessous. Par exemple, une classe de fractions:

class Fraction {
    int num;
    int denom;
    . . .
    public:
    . . .
    bool operator < (const Fraction &other) {
        if ((this->num * other.denom) < (this->denom * other.num))
            return false;
        else
            return true;
       }
       bool operator == (const Fraction &other) (
           if (!(*this < other) && !(other < *this)) {
               return true;
           else
               return false;
       }
};
1
Prithwish Jana