web-dev-qa-db-fra.com

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

(Remarque: Tuple et tie peuvent être extraits de Boost ou C++ 11.)
Lors de l'écriture de petites structures avec seulement deux éléments, j'ai parfois tendance à choisir un std::pair, car tout ce qui est important est déjà fait pour ce type de données, comme operator< pour un ordre strict-faible.
Les inconvénients sont cependant les noms de variables à peu près inutiles. Même si j'ai moi-même créé ce typedef, je ne me souviendrai pas 2 jours plus tard de ce que first et de ce que second était exactement, surtout s'ils sont tous les deux du même type. Cela devient encore pire pour plus de deux membres, car l'imbrication de pair est à peu près nulle.
L'autre option pour cela est un Tuple, soit de Boost soit de C++ 11, mais cela ne semble pas vraiment plus agréable et plus clair. Je reviens donc à l'écriture des structures moi-même, y compris tous les opérateurs de comparaison nécessaires.
Puisque surtout le operator< peut être assez lourd, 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);
}

(tie fait un Tuple de T& références des arguments passés.)


Edit: La suggestion de @DeadMG d'hériter en privé de Tuple n'est pas mauvaise, mais elle a eu quelques inconvénients:

  • Si les opérateurs sont autonomes (éventuellement des amis), je dois hériter publiquement
  • Avec le casting, mes fonctions/opérateurs (operator= spécifiquement) peut être facilement contourné
  • Avec la solution tie, je peux omettre certains membres s'ils n'ont pas d'importance pour la commande

Y a-t-il des inconvénients dans cette implémentation que je dois considérer?

95
Xeo

Cela va certainement faciliter l'écriture d'un opérateur correct plutôt que de le faire rouler vous-même. Je dirais ne considérer une approche différente que 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.

58
Mark B

Je suis tombé sur ce même problème et ma solution utilise des modèles variadic 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
);
5
user2369060

À mon avis, vous n'abordez toujours pas le même problème que le std::Tuple résout - à savoir, vous devez savoir à la fois le nombre et le nom de chaque variable membre, vous la dupliquez deux fois dans la fonction. Vous pouvez opter pour l'héritage private.

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

Cette approche est un petit peu plus de gâchis pour commencer, mais vous ne maintenez les variables et les noms qu'à un seul endroit, plutôt qu'à chaque endroit pour chaque opérateur que vous souhaitez surcharger.

3
Puppy

Si vous prévoyez d'utiliser plus d'une surcharge d'opérateur 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, ce que vous faites est beaucoup plus de travail. Lorsque vous décidez entre les deux, une question importante à laquelle 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.

1
Lee Louviere