Implémenter des opérateurs de comparaison via 'tuple' et 'tie', une bonne idée?

98

(Remarque: tupleet tiepeut être extrait de Boost ou C ++ 11.)
Lors de l'écriture de petites structures avec seulement deux éléments, j'ai parfois tendance à choisir a std::pair, car toutes les choses importantes sont déjà faites pour ce type de données, comme operator<pour l'ordre strict-faible .
Les inconvénients sont cependant les noms de variables à peu près inutiles. Même si je l'ai moi-même créé typedef, je ne me souviendrai pas 2 jours plus tard de ce firstque secondc'était et de ce que c'était exactement, surtout s'ils sont tous les deux du même type. Cela devient encore pire pour plus de deux membres, car la nidification pairest vraiment nul.
L'autre option pour cela est untuple, que ce soit à partir de Boost ou de C ++ 11, mais cela n'a pas vraiment l'air plus beau et plus clair. Je reviens donc à l'écriture des structures moi-même, y compris les opérateurs de comparaison nécessaires.
Puisque surtout cela operator<peut être assez encombrant, j'ai pensé à contourner tout ce gâchis en me basant simplement sur les opérations définies pour tuple:

Exemple de operator<, par exemple pour un ordre strict-faible:

bool operator<(MyStruct const& lhs, MyStruct const& rhs){
  return std::tie(lhs.one_member, lhs.another, lhs.yet_more) <
         std::tie(rhs.one_member, rhs.another, rhs.yet_more);
}

( tiefait tupledes T&références à partir des arguments passés.)


Edit : La suggestion de @DeadMG d'hériter en privé tuplen'est pas mauvaise, mais elle présente de nombreux inconvénients:

  • Si les opérateurs sont autonomes (éventuellement amis), je dois hériter publiquement
  • Avec le casting, mes fonctions / opérateurs (en operator=particulier) peuvent être facilement contournés
  • Avec la tiesolution, je peux laisser de côté certains membres s'ils ne comptent pas pour la commande

Y a-t-il des inconvénients dans cette implémentation que je dois prendre en compte?

Xeo
la source
1
Cela
1
C'est une idée très intelligente, même si cela ne fonctionne pas. Je vais devoir enquêter là-dessus.
templatetypedef
Cela semble assez raisonnable. Le seul écueil auquel je peux penser maintenant est qu'il tiene peut pas être appliqué aux membres de champ de bits.
Ise Wisteria le
4
J'aime cette idée! Si les tie(...)appels doivent être dupliqués dans divers opérateurs (=, ==, <, etc.), vous pouvez écrire une méthode en ligne privée make_tuple(...)pour encapsuler cela, puis l'appeler à partir de divers autres endroits, comme dans return lhs.make_tuple() < rhs.make_tuple();(bien que le type de retour de cette méthode pourrait être amusante à déclarer!)
aldo
13
@aldo: C ++ 14 à la rescousse! auto tied() const{ return std::tie(the, members, here); }
Xeo

Réponses:

60

Cela rendra certainement plus facile d'écrire un opérateur correct que de le lancer vous-même. Je dirais qu'envisager une approche différente uniquement si le profilage montre que l'opération de comparaison est une partie chronophage de votre application. Sinon, la facilité de maintenance devrait l'emporter sur les éventuels problèmes de performances.

Marque B
la source
17
Je ne peux pas imaginer un cas où tuple<>l » operator<seraient plus lentement qu'une signature manuscrite.
ildjarn
51
J'ai eu exactement la même idée une fois et j'ai fait quelques expériences. A été positivement surpris de voir que le compilateur a intégré et optimisé tout ce qui concerne les tuples et les références, émettant un assemblage presque identique au code écrit à la main.
JohannesD
7
@JohannesD: Je peux soutenir ce témoignage, fait la même chose une fois
sehe
Cela garantit-il une commande stricte et faible ? Comment?
CinCout
5

Je suis tombé sur le même problème et ma solution utilise des modèles variadiques c ++ 11. Voici le code:

La partie .h:

/***
 * Generic lexicographical less than comparator written with variadic templates
 * Usage:
 *   pass a list of arguments with the same type pair-wise, for intance
 *   lexiLessthan(3, 4, true, false, "hello", "world");
 */
bool lexiLessthan();

template<typename T, typename... Args>
bool lexiLessthan(const T &first, const T &second, Args... rest)
{
  if (first != second)
  {
    return first < second;
  }
  else
  {
    return lexiLessthan(rest...);
  }
}

Et le .cpp pour le cas de base sans arguments:

bool lexiLessthan()
{
  return false;
}

Maintenant, votre exemple devient:

return lexiLessthan(
    lhs.one_member, rhs.one_member, 
    lhs.another, rhs.another, 
    lhs.yet_more, rhs.yet_more
);
user2369060
la source
J'ai mis une solution similaire ici mais ne nécessitant pas l'opérateur! =. stackoverflow.com/questions/11312448/…
steviekm3
3

À mon avis, vous ne std::tuplerésolvez toujours pas le même problème que la solution - à savoir, vous devez savoir à la fois combien et le nom de chaque variable membre, vous la dupliquez deux fois dans la fonction. Vous pouvez opter pour l' privatehéritage.

struct somestruct : private std::tuple<...> {
    T& GetSomeVariable() { ... }
    // etc
};

Cette approche est un peu plus compliquée pour commencer, mais vous ne conservez les variables et les noms qu'au même endroit, au lieu de chaque endroit pour chaque opérateur que vous souhaitez surcharger.

Chiot
la source
3
J'utiliserais donc des accesseurs nommés pour les variables comme T& one_member(){ return std::get<0>(*this); }etc? Mais cela n'aurait-il pas besoin de moi pour fournir une telle méthode pour chaque "membre" que j'ai, y compris les surcharges pour la version const et non-const?
Xeo
@Xeo Je ne vois pas les accesseurs nommés comme nécessitant plus de travail que la création de variables réelles. Dans tous les cas, vous devrez avoir un nom distinct pour chaque variable. Je suppose qu'il y aurait une duplication pour const / non-const. Cependant, vous pouvez modéliser tout ce travail.
Lee Louviere
1

Si vous prévoyez d'utiliser plus d'une surcharge d'opérateurs, ou plusieurs méthodes de tuple, je vous recommande de faire de tuple un membre de la classe ou de dériver de tuple. Sinon, vous faites beaucoup plus de travail. Lorsque vous décidez entre les deux, une question importante à laquelle il faut répondre est: Voulez-vous que votre classe soit un tuple? Sinon, je recommanderais de contenir un tuple et de limiter l'interface en utilisant la délégation.

Vous pouvez créer des accesseurs pour «renommer» les membres du tuple.

Lee Louvière
la source
Je lis la question de l'OP comme signifiant « est mise en œuvre de ma classe à l' operator<aide std::tieraisonnable? » Je ne comprends pas comment cette réponse se rapporte à cette question.
ildjarn
@ildjarn Il y a des commentaires que je n'ai pas postés ici. J'ai tout compilé pour qu'il se lit mieux.
Lee Louviere