web-dev-qa-db-fra.com

Quels sont les bons cas d'utilisation des tuples en C ++ 11?

Quels sont les bons cas d'utilisation pour utiliser des tuples en C++ 11? Par exemple, j'ai une fonction qui définit une structure locale comme suit:

template<typename T, typename CmpF, typename LessF>
void mwquicksort(T *pT, int nitem, const int M, CmpF cmp, LessF less)
{
  struct SI
  {
    int l, r, w;
    SI() {}
    SI(int _l, int _r, int _w) : l(_l), r(_r), w(_w) {}
  } stack[40];

  // etc

J'envisageais de remplacer la structure SI par un std::Tuple<int,int,int>, Qui est une déclaration beaucoup plus courte avec des constructeurs et des opérateurs pratiques déjà prédéfinis, mais avec les inconvénients suivants:

  • Les éléments de tuple sont cachés dans des structures obscures définies par l'implémentation. Même si Visual studio interprète et affiche bien leur contenu, je ne peux toujours pas mettre de points d'arrêt conditionnels qui dépendent de la valeur des éléments Tuple.
  • L'accès aux champs Tuple individuels (get<0>(some_Tuple)) est beaucoup plus détaillé que l'accès aux éléments struct (s.l).
  • L'accès aux champs par nom est beaucoup plus informatif (et plus court!) Que par index numérique.

Les deux derniers points sont quelque peu traités par la fonction tie. Compte tenu de ces inconvénients, quel serait un bon cas d'utilisation pour les tuples?

UPDATE Il s'avère que le débogueur VS2010 SP1 ne peut pas afficher le contenu du tableau suivant std::Tuple<int, int, int> stack[40], mais cela fonctionne bien quand il est codé avec une structure. La décision est donc une évidence: si vous devez inspecter ses valeurs, utilisez une structure [esp. important avec des débogueurs comme GDB].

40
zvrba

Eh bien, à mon humble avis, la partie la plus importante est le code générique. Écrire du code générique qui fonctionne sur toutes sortes de structures est beaucoup plus difficile que d'écrire des génériques qui fonctionnent sur des tuples. Par exemple, le std::tie la fonction que vous avez mentionnée vous-même serait quasiment impossible à réaliser pour les structures.

cela vous permet de faire des choses comme ça:

  • Stocker les paramètres de fonction pour une exécution retardée (par exemple cette question )
  • Renvoie plusieurs paramètres sans encombrement (dés) encombrant avec std::tie
  • Combinez des ensembles de données (de type différent) (par exemple à partir d'une exécution parallèle), cela peut être fait aussi simplement que std::Tuple_cat.

Le fait est que cela ne s'arrête pas à ces utilisations, les gens peuvent développer cette liste et écrire des fonctionnalités génériques basées sur des tuples, ce qui est beaucoup plus difficile à faire avec les structures. Qui sait, peut-être que demain quelqu'un trouvera une utilisation brillante à des fins de sérialisation.

26
KillianDS

C'est un moyen facile de renvoyer plusieurs valeurs d'une fonction;

std::Tuple<int,int> fun();

Les valeurs de résultat peuvent être utilisées avec élégance comme suit:

int a;
int b;
std::tie(a,b)=fun();
57
mirk

Je pense que la plupart des utilisations de Tuples proviennent de std::tie:

bool MyStruct::operator<(MyStruct const &o) const
{
    return std::tie(a, b, c) < std::tie(o.a, o.b, o.c);
}

Avec de nombreux autres exemples dans les réponses ici. Cependant, je trouve que cet exemple est le plus couramment utile, car il économise beaucoup d'efforts par rapport à ce qu'il était en C++ 03.

20
LB--

Avez-vous déjà utilisé std::pair? La plupart des endroits que vous utiliseriez std::Tuple sont similaires, mais ne se limitent pas à exactement deux valeurs.

Les inconvénients que vous indiquez pour les tuples s'appliquent également à std :: pair, parfois vous voulez un type plus expressif avec de meilleurs noms pour ses membres que first et second, mais parfois vous n'en avez pas besoin . La même chose s'applique aux tuples.

13
Jonathan Wakely

Je pense qu'il n'y a PAS de bon usage pour les tuples en dehors des détails d'implémentation d'une fonctionnalité générique de bibliothèque.

Les (possibles) économies de frappe ne compensent pas les pertes de propriétés auto-documentées du code résultant.

Substituer des tuples à des structures qui enlève simplement un nom significatif à un champ, en remplaçant le nom du champ par un "nombre" (tout comme le concept mal conçu d'une std :: pair).

Renvoyer plusieurs valeurs à l'aide de tuples est beaucoup moins auto-documenté que les alternatives - renvoyer des types nommés ou utiliser des références nommées. Sans cette auto-documentation, il est facile de confondre l'ordre des valeurs retournées, si elles sont mutuellement convertibles.

11
igorlord

Les cas d'utilisation réels sont des situations où vous avez des éléments innommables - des modèles variadiques et des fonctions lambda. Dans les deux situations, vous pouvez avoir des éléments sans nom avec des types inconnus et donc la seule façon de les stocker est une structure avec des éléments sans nom: std :: Tuple. Dans toutes les autres situations, vous avez un nombre connu d'éléments pouvant être nommés avec des types connus et pouvez donc utiliser une structure ordinaire, qui est la réponse supérieure 99% du temps.

Par exemple, vous ne devez PAS utiliser std :: Tuple pour avoir des "retours multiples" à partir de fonctions ou de modèles ordinaires avec un nombre fixe d'entrées génériques. Utilisez une vraie structure pour cela. Un véritable objet est BEAUCOUP plus "générique" que l'emporte-pièce std :: Tuple, car vous pouvez donner un véritable objet littéralement n'importe quelle interface. Il vous donnera également beaucoup plus de sécurité et de flexibilité dans les bibliothèques publiques.

Il suffit de comparer ces 2 fonctions de membre de classe:

std::Tuple<double, double, double>  GetLocation() const; // x, y, z

GeoCoordinate  GetLocation() const;

Avec un véritable objet "géo-coordonnées", je peux fournir un opérateur bool () qui retourne false si l'objet parent n'a pas d'emplacement. Via ses API, les utilisateurs pouvaient obtenir les emplacements x, y, z. Mais voici la grande chose: si je décide de créer GeoCoordinate 4D en ajoutant un champ temporel dans 6 mois, le code des utilisateurs actuels ne se cassera pas. Je ne peux pas faire ça avec la version std :: Tuple.

6
Zack Yezek

Je ne peux pas commenter la réponse de mirk, donc je vais devoir donner une réponse distincte:

Je pense que les tuples ont également été ajoutés à la norme pour permettre une programmation de style fonctionnel. Par exemple, alors que le code comme

void my_func(const MyClass& input, MyClass& output1, MyClass& output2, MyClass& output3)
{
   // whatever
}

est omniprésent en C++ traditionnel, car c'est le seul moyen de renvoyer plusieurs objets par une fonction, c'est une abomination pour la programmation fonctionnelle. Vous pouvez maintenant écrire

Tuple<MyClass, MyClass, MyClass> my_func(const MyClass& input)
{
   // whatever
   return Tuple<MyClass, MyClass, MyClass>(output1, output2, output3);
}

Ainsi, vous avez la possibilité d'éviter les effets secondaires et la mutabilité, de permettre le pipelining et, en même temps, de préserver la force sémantique de votre fonction.

1
julio

L'interopérabilité avec d'autres langages de programmation qui utilisent des tuples et le renvoi de plusieurs valeurs sans que l'appelant doive comprendre les types supplémentaires. Ce sont les deux premiers qui me viennent à l'esprit.

1
John Zwinck