238 votes

Quel est l'opérateur <=> ("spaceship", comparaison à trois voies) en C++?

Alors que j'essayais d'apprendre les opérateurs C++, je suis tombé sur un étrange opérateur de comparaison sur cppreference.com,* dans un tableau qui ressemblait à ceci :

saisissez ici la description de l'image

"Eh bien, si ce sont des opérateurs courants en C++, je ferais mieux de les apprendre", je me suis dit. Mais toutes mes tentatives pour élucider ce mystère ont été infructueuses. Même ici, sur Stack Overflow, je n'ai pas eu de chance dans ma recherche.

Y a-t-il un lien entre <=> et C++ ?

Et s'il y en a un, que fait exactement cet opérateur ?

* Entre-temps, cppreference.com a mis à jour cette page et contient maintenant des informations sur l'opérateur <code><=></code>.

1 votes

@cubuspl42 bar< foo::operator<=> est un exemple de comment cela pourrait ressembler à l'opérateur <--.

9 votes

@haccks: Tout à fait. Comme C++11 est une étiquette sur les compilateurs qui implémentent C++11. Et C++14 est une étiquette sur les compilateurs qui implémentent C++14. Et C++17 concerne les compilateurs qui implémentent C++17. Non, le C++20 est l'étiquette pour les éléments concernant le C++20. Et comme cette question concerne le C++20, le voici. Le wiki de l'étiquette était incorrect, pas l'étiquette elle-même.

192voto

Rxmsc Points 927

Cela s'appelle l'opérateur de comparaison à trois voies.

Selon la proposition P0515 :

Il y a un nouvel opérateur de comparaison à trois voies, <=>. L'expression a <=> b renvoie un objet qui compare <0 si a < b, compare >0 si a > b, et compare ==0 si a et b sont égaux/équivalents.

Pour écrire toutes les comparaisons pour votre type, il suffit d'écrire operator<=> qui renvoie le type de catégorie approprié :

  • Renvoyez an _ordering si votre type supporte naturellement <, et nous générerons efficacement <, >, <=, >=, ==, et !=; sinon renvoyez an _equality, et nous générerons efficacement \== et !=.

  • Renvoyez fort si pour votre type a == b implique f(a) == f(b) (substituabilité, où f lit uniquement l'état de comparaison saillante accessible en utilisant l'interface const non privée), sinon renvoyez faible.

Le cppreference dit :

Les expressions de l'opérateur de comparaison à trois voies ont la forme

lhs <=> rhs   (1)  

L'expression renvoie un objet qui

  • compare <0 si lhs < rhs
  • compare >0 si lhs > rhs
  • et compare ==0 si lhs et rhs sont égaux/équivalents.

122 votes

Pour ceux qui sont confus (comme je l'étais) sur ce que signifient "compare <0", "compare >0", et "compare ==0", cela signifie que <=> renvoie une valeur négative, positive ou nulle, en fonction des arguments. Tout comme strncmp et memcmp.

0 votes

Le premier point indique que si < est défini, le compilateur générera == - mais comment est-ce possible? 'a' < 'b' == 1, mais à la fois 'a' < 'a' et 'c' < 'a' sont tous les deux == 0 donc il ne devrait pas être possible de déduire une comparaison à utiliser par == sans informations supplémentaires.

1 votes

@Dai même si à la fois 'a' < 'a' et 'c' < 'a' sont tous les deux faux, 'a' < 'a' et 'a' < 'c' ne le sont pas. Dans un ordre strict, les affirmations suivantes sont vraies : a != b a < b || b < a

123voto

q-l-p Points 2149

Le 11-11-2017, le comité ISO C++ a adopté la proposition de Herb Sutter pour l'opérateur de comparaison à trois voies "<=>" (aussi connu comme l'opérateur "vaisseau spatial") comme l'une des nouvelles fonctionnalités ajoutées à C++20. Dans le document intitulé Comparaison cohérente Sutter, Maurer et Brown démontrent les concepts de la nouvelle conception. Pour un aperçu de la proposition, voici un extrait de l'article :

L'expression a <=> b retourne un objet qui compare <0 si a < b, compare >0 si a > b, et compare \==0 si a et b sont égaux/équivalents.

Cas classique : Pour écrire toutes les comparaisons pour votre type X avec le type Y, avec une sémantique membre par membre, écrivez simplement :

auto X::operator<=>(const Y&) =default;

Cas avancés : Pour écrire toutes les comparaisons pour votre type X avec le type Y, écrivez simplement un operator<=> qui prend un Y, peut utiliser \=default pour obtenir une sémantique membre par membre si désiré, et retourne le type de catégorie approprié :

  • Retournez un _ordering si votre type supporte naturellement <, et nous allons générer efficacement les symétriques <, >, <=, >=, \==, et !=; sinon retournez un _equality, et nous allons générer efficacement les symétriques \== et !=.
  • Retournez strong_ si pour votre type a == b implique f(a) == f(b) (substituabilité, où f ne fait que lire l'état de comparaison accessible en utilisant les membres publics const), sinon retournez weak_.

Catégories de Comparaison

Cinq catégories de comparaison sont définies en tant que types std::, chacune ayant les valeurs prédéfinies suivantes :

+--------------------------------------------------------------------+
|                  |          Valeurs numériques          | Valeurs non-numériques |
|     Catégorie     +-----------------------------------+             |
|                  | -1   | 0          | +1            |   valeurs    |
+------------------+------+------------+---------------+-------------+
| strong_ordering  | moins | égal       | plus          |             |
| weak_ordering    | moins | équivalent | plus          |             |
| partial_ordering | moins | équivalent | plus          | unordered     |
| strong_equality  |      | égal       | négal         |              |
| weak_equality    |      | équivalent | non-équivalent|             |
+------------------+------+------------+---------------+-------------+

Les conversions implicites entre ces types sont définies comme suit :

  • strong_ordering avec les valeurs {moins, égal, plus} se convertit implicitement en :
    • weak_ordering avec les valeurs {moins, équivalent, plus}
    • partial_ordering avec les valeurs {moins, équivalent, plus}
    • strong_equality avec les valeurs {non égal, égal, non égal}
    • weak_equality avec les valeurs {non-équivalent, équivalent, non-équivalent}
  • weak_ordering avec les valeurs {moins, équivalent, plus} se convertit implicitement en :
    • partial_ordering avec les valeurs {moins, équivalent, plus}
    • weak_equality avec les valeurs {non-équivalent, équivalent, non-équivalent}
  • partial_ordering avec les valeurs {moins, équivalent, plus, unordered} se convertit implicitement en :
    • weak_equality avec les valeurs {non-équivalent, équivalent, non-équivalent, non-équivalent}
  • strong_equality avec les valeurs {égal, non égal} se convertit implicitement en :
    • weak_equality avec les valeurs {équivalent, non-équivalent}

Comparaison à Trois Voies

Le token <=> est introduit. La séquence de caractères <=> se tokenise en <= >, dans le vieux code source. Par exemple, X<&Y::operator<=> doit ajouter un espace pour conserver sa signification.

Le <=> est une fonction de comparaison à trois voies surchargeable et a une précédence supérieure à < et inférieure à <<. Il retourne un type qui peut être comparé avec littéral 0 mais d'autres types de retour sont autorisés pour supporter les modèles d'expressions. Tous les opérateurs <=> définis dans le langage et dans la bibliothèque standard retournent l'une des 5 catégories de comparaison std:: mentionnées ci-dessus.

Pour les types de langage, les comparaisons de mêmes types intégrées suivantes sont fournies. Toutes sont constexpr, sauf indication contraire. Ces comparaisons ne peuvent pas être invoquées de manière hétérogène en utilisant des promotions/conversions scalaires.

  • Pour les typesbool, intégraux, et pointeur,<=>retournestrong_ordering.
  • Pour les types de pointeur, les différentes cv-qualifications et conversions dérivées à de base sont autorisées pour invoquer une<=> intégrée homogène, et il existe uneoperator<=>(T*, nullptr_t) intégrée hétérogène. Seules les comparaisons de pointeurs vers le même objet/alllocation sont des expressions constantes.
  • Pour les types de nombres à virgule flottante fondamentaux,<=>retournepartial_ordering, et peut être invoqué de manière hétérogène en élargissant les arguments à un type de nombre à virgule flottante plus grand.
  • Pour les énumérations,<=>retourne la même chose que le type sous-jacent de l'énumération<=>.
  • Pournullptr_t,<=>retournestrong_orderinget donne toujourségal.
  • Pour les tableaux copiables,T[N] <=> T[N]retourne le même type que le<=>deTet effectue une comparaison lexicographique élément par élément. Il n'y a pas de<=>pour d'autres tableaux.
  • Pourvoidil n'y a pas de<=>.

Pour mieux comprendre le fonctionnement de cet opérateur, veuillez lire le document original <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0515r2.pdf" rel="noreferrer">ici</a>. Ceci est juste ce que j'ai découvert en utilisant des moteurs de recherche.

3 votes

Comme si cpp n'était pas déjà assez complexe. Pourquoi ne pas simplement écrire une méthode de comparaison...

9 votes

@Leandro L'opérateur de vaisseau spatial est cette méthode de comparaison. De plus, cela fonctionne simplement et écrit (ou supprime) les six autres opérateurs de comparaison. Je préfère une seule fonction d'opérateur de comparaison écrite à six gabarits individuels.

0 votes

Notez que les types _equality sont morts : il s'est avéré que <=> fonctionne bien avec les quatre opérateurs relationnels mais moins bien avec les deux opérateurs d'égalité (bien qu'il y ait du sucre syntaxique intense pour prendre en charge le cas courant où vous les voulez tous).

12voto

Stig Hemmer Points 2175

Cette réponse est devenue obsolète car la page web référencée a changé

La page web à laquelle vous faites référence était cassée. Elle était en cours d'édition ce jour-là et différentes parties n'étaient pas synchronisées. Lorsque je l'ai consultée, le statut était le suivant :

En haut de la page, elle répertorie les opérateurs de comparaison actuellement existants (en C++14). Il n'y a pas de <=> là-bas.

En bas de la page, ils auraient dû répertorier les mêmes opérateurs, mais ils se sont trompés et ont ajouté cette suggestion future.

gcc ne connait pas encore <=> (et avec -std=c++14, ne le fera jamais), alors il pense que vous avez voulu dire a <= > b. Cela explique le message d'erreur.

Si vous essayez la même chose dans cinq ans, vous obtiendrez probablement un meilleur message d'erreur, quelque chose comme <=> ne fait pas partie de C++14.

1 votes

La page web à laquelle OP renvoie est correcte, tout comme la page séparée à laquelle vous renvoyez. Il qualifie l'opérateur <=> avec l'étiquette (depuis C++20), vous indiquant dans quelle version de la norme vous pouvez vous attendre à le trouver. Le balisage des normes est une convention que suit cppreference.com. Bien sûr, vous n'avez pas de compilateur qui revient dans le temps pour le prendre en charge, mais cppreference vous dit (correctement) à quoi vous attendre.

0 votes

Oui, mais... Pas une réponse. Vous commentez... ou quelque chose.

2 votes

J'avais l'intention de mettre un lien vers la même page web que la question, mais j'ai raté. Je pense avoir répondu aux parties de la question auxquelles les autres réponses n'ont pas répondu. J'ai ignoré la principale question en gras car d'autres l'avaient déjà répondu.

7voto

Ciro Santilli Points 3341

Le fait de rendre par défaut <=> donne automatiquement ==, !=, <, >, <=, >= gratuitement

C++20 a une nouvelle fonctionnalité de "comparaison par défaut" configurée de telle sorte que le fait de définir par défaut <=> donne toutes les autres gratuitement. Je crois que c'est la principale motivation derrière l'ajout de operator<=>.

Adapté de https://en.cppreference.com/w/cpp/language/default_comparisons:

main.cpp

#include 
#include 
#include 

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

int main() {
    Point pt1{1, 1}, pt2{1, 2};

    // Juste pour montrer que cela suffit pour `std::set`.
    std::set s;
    s.insert(pt1);

    // Toutes ces comparaisons sont automatiquement définies pour nous!
    assert(!(pt1 == pt2));
    assert( (pt1 != pt2));
    assert( (pt1 <  pt2));
    assert( (pt1 <= pt2));
    assert(!(pt1 >  pt2));
    assert(!(pt1 >= pt2));
}

compiler et exécuter:

sudo apt install g++-10
g++-10 -ggdb3 -O0 -std=c++20 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

Une version équivalente plus explicite de ce qui précède serait:

struct Point {
    int x;
    int y;
    auto operator<=>(const Point& other) const {
        if (x < other.x) return -1;
        if (x > other.x) return 1;
        if (y < other.y) return -1;
        if (y > other.y) return 1;
        return 0;
    }
    bool operator==(const Point& other) const = default;
};

Dans ce cas, nous devons définir explicitement bool operator==(const Point& other) const = default; car si operator<=> n'est pas défini par défaut (par exemple, comme donné explicitement ci-dessus), alors operator== n'est pas automatiquement défini:

Conformément aux règles de surcharge de n'importe quel operator<=>, une surcharge de <=> définie par défaut permettra également de comparer le type avec <, <=, >, et >=.

Si operator<=> est défini par défaut et que operator== n'est pas du tout déclaré, alors operator== est implicitement défini par défaut.

L'exemple ci-dessus utilise le même algorithme que le operator<=> par défaut, tel qu'expliqué par cppreference:

Le operator<=> par défaut effectue une comparaison lexicographique en comparant successivement les sous-objets de T de base (en profondeur de gauche à droite) puis non statiques (dans l'ordre de déclaration) pour calculer <=>, en étendant récursivement les membres du tableau (dans l'ordre d'index croissant), et s'arrêtant prématurément lorsqu'un résultat de non-égalité est trouvé

Avant C++20, vous ne pouviez pas faire quelque chose comme operator== = default, et définir un opérateur ne conduirait pas à la définition des autres, par exemple, ce qui suit ne compile pas avec -std=c++17:

#include 

struct Point {
    int x;
    int y;
    auto operator==(const Point& other) const {
        return x == other.x && y == other.y;
    };
};

int main() {
    Point pt1{1, 1}, pt2{1, 2};

    // Faire quelques vérifications.
    assert(!(pt1 == pt2));
    assert( (pt1 != pt2));
}

avec l'erreur:

main.cpp:16:18: erreur : pas de correspondance pour ‘operator!=’ (les types d'opérandes sont ‘Point’ et ‘Point’)
   16 |     assert( (pt1 != pt2));
      |              ~~~ ^~ ~~~
      |              |      |
      |              Point  Point

Cependant, ce qui précède compile sous -std=c++20.

Lié : Les surcharges d'opérateurs C++ sont-elles automatiquement fournies en fonction des autres?

Testé sur Ubuntu 20.04, GCC 10.2.0.

0voto

Nithin R Points 411

L'opérateur de comparaison triple (<=>) est introduit en C++ 20.

Cette expression renvoie l'objet comme ci-dessous;

auto cmp  = a <=> b;

cmp > 0 si a > b
cmp = 0 si a == b
cmp < 0 si a < b  

Programme d'exemple

#include 

using namespace std;

int main()
{
        int lhs = 10, rhs = 20;
        auto result = lhs <=> rhs;

        if (result < 0) {
                cout << "lhs est inférieur à rhs" << endl;
        }
        else if (result > 0) {
                cout << "lhs est supérieur à rhs" << endl;
        }
        else {
                cout << "lhs et rhs sont égaux" << endl;
        }

}

Comment compiler et exécuter ?

g++-10 threewaycmp.cpp -std=c++20
./a.out

Résultat

lhs est inférieur à rhs

Veuillez vous référer au lien ci-dessous pour plus de détails https://en.cppreference.com/w/cpp/language/operator_comparison

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X