Pourquoi l’utilisation de tuples en C ++ n’est-elle pas plus courante?

Pourquoi personne ne semble utiliser des tuples en C ++, ni la bibliothèque Boost Tuple ni la bibliothèque standard pour TR1? J’ai lu beaucoup de code C ++, et très rarement je vois l’utilisation de tuples, mais je vois souvent beaucoup d’endroits où les tuples résoudraient beaucoup de problèmes (retournant généralement plusieurs valeurs à partir de fonctions).

Les tuples vous permettent de faire toutes sortes de choses intéressantes comme ceci:

tie(a,b) = make_tuple(b,a); //swap a and b 

C’est certainement mieux que cela:

 temp=a; a=b; b=temp; 

Bien sûr, vous pouvez toujours faire ceci:

 swap(a,b); 

Mais que faire si vous voulez faire pivoter trois valeurs? Vous pouvez le faire avec des tuples:

 tie(a,b,c) = make_tuple(b,c,a); 

Les tuples facilitent également le retour de variables multiples à partir d’une fonction, ce qui est probablement un cas beaucoup plus courant que l’échange de valeurs. L’utilisation de références pour renvoyer des valeurs n’est certainement pas très élégante.

Y a-t-il de gros inconvénients aux tuples auxquels je ne pense pas? Sinon, pourquoi sont-ils rarement utilisés? Sont-ils plus lents? Ou est-ce juste que les gens ne sont pas habitués à eux? Est-ce une bonne idée d’utiliser des tuples?

Parce que ce n’est pas encore standard. Tout ce qui n’est pas standard présente un obstacle beaucoup plus important. Pieces of Boost est devenu populaire parce que les programmeurs les réclamaient. (hash_map fait un saut à l’esprit). Mais bien que le tuple soit pratique, ce n’est pas une victoire si évidente que les gens s’en préoccupent.

Une réponse cynique est que beaucoup de gens programment en C ++, mais ne comprennent pas et / ou n’utilisent pas les fonctionnalités de haut niveau. Parfois, c’est parce qu’ils ne sont pas autorisés, mais beaucoup ne font tout simplement pas (ou même ne comprennent pas).

Comme exemple non-boost: combien de personnes utilisent la fonctionnalité trouvée dans ?

En d’autres termes, de nombreux programmeurs C ++ sont simplement des programmeurs C utilisant des compilateurs C ++, et peut-être std::vector et std::list . C’est une des raisons pour lesquelles l’utilisation de boost::tuple n’est pas plus courante.

La syntaxe du tuple C ++ peut être un peu plus détaillée que ce que souhaiteraient la plupart des gens.

Considérer:

 typedef boost::tuple MyTuple; 

Donc, si vous voulez faire un usage intensif des tuples, vous obtenez soit des typedefs de tuple partout ou vous obtenez des noms de types long et ennuyeux partout. J’aime les tuples. Je les utilise si nécessaire. Mais il est généralement limité à quelques situations, comme un index N-element ou lors de l’utilisation de multimaps pour lier les paires d’iterators d’intervalle. Et c’est généralement dans une scope très limitée.

Tout est très laid et hacké par rapport à quelque chose comme Haskell ou Python. Lorsque C ++ 0x arrive ici et que nous obtenons le mot-clé ‘auto’, les tuples commenceront à paraître plus attrayants.

L’utilité des tuples est inversement proportionnelle au nombre de frappes nécessaires pour les déclarer, les emballer et les déballer.

Pour moi, c’est une habitude, haut la main: les tuples ne résolvent aucun nouveau problème pour moi, juste quelques-uns que je peux déjà gérer très bien. Échanger des valeurs semble encore plus facile à l’ancienne – et, plus important encore, je ne pense pas vraiment à la façon de changer «mieux». C’est assez bon tel quel.

Personnellement, je ne pense pas que les tuples soient une excellente solution pour renvoyer plusieurs valeurs – cela ressemble à un travail pour les struct .

Mais que faire si vous voulez faire pivoter trois valeurs?

 swap(a,b); swap(b,c); // I knew those permutation theory lectures would come in handy. 

OK, donc avec les valeurs 4 etc, le n-tuple devient éventuellement moins de code que n-1 swaps. Et avec le swap par défaut, cela fait 6 affectations au lieu de 4 si vous implémentiez vous-même un modèle à trois cycles, mais j’espère que le compilateur résoudra ce problème pour les types simples.

Vous pouvez proposer des scénarios dans lesquels les swaps sont lourds ou inappropriés, par exemple:

 tie(a,b,c) = make_tuple(b*c,a*c,a*b); 

est un peu difficile à défaire.

Le point est, cependant, il existe des moyens connus pour faire face aux situations les plus courantes pour lesquelles les tuples sont bons et, par conséquent, il n’est pas urgent de prendre des tuples. Si rien d’autre, je ne suis pas sûr que:

 tie(a,b,c) = make_tuple(b,c,a); 

ne fait pas 6 copies, ce qui le rend totalement inadapté à certains types (les collections étant les plus évidentes). N’hésitez pas à me convaincre que les tuples sont une bonne idée pour les “grands” types, en disant que ce n’est pas le cas 🙂

Pour renvoyer plusieurs valeurs, les tuples sont parfaits si les valeurs sont de types incompatibles, mais certaines personnes ne les aiment pas s’il est possible pour l’appelant de les obtenir dans le mauvais ordre. Certaines personnes n’aiment pas les valeurs de retour multiples et ne veulent pas encourager leur utilisation en les rendant plus faciles. Certaines personnes préfèrent simplement les structures nommées pour les parameters d’entrée et de sortie, et ne pourraient probablement pas être persuadées avec une batte de baseball pour utiliser des tuples. Pas de comptabilité pour le goût.

Comme de nombreuses personnes l’ont fait remarquer, les tuples ne sont pas aussi utiles que d’autres fonctionnalités.

  1. Les trucs d’échange et de rotation ne sont que des gadgets. Ils sont complètement déroutants pour ceux qui ne les ont pas vus auparavant, et puisque c’est à peu près tout le monde, ces trucs ne sont que de mauvaises pratiques d’ingénierie logicielle.

  2. Renvoyer plusieurs valeurs en utilisant des tuples est beaucoup moins auto-documenté que les alternatives – retour des types nommés ou utilisation des références nommées. Sans cette auto-documentation, il est facile de confondre l’ordre des valeurs renvoyées, si elles sont mutuellement convertibles, et de ne pas être plus sage.

Tout le monde ne peut pas utiliser boost, et TR1 n’est pas encore largement disponible.

Lorsque vous utilisez C ++ sur des systèmes embarqués, l’extraction des bibliothèques Boost est complexe. Ils se couplent pour que la taille de la bibliothèque augmente. Vous renvoyez des structures de données ou utilisez le passage de parameters au lieu de tuples. Lorsque vous retournez des tuples dans Python, la structure de données est dans l’ordre et le type des valeurs renvoyées, ce n’est tout simplement pas explicite.

Certes, les tuples peuvent être utiles, mais comme mentionné précédemment, il faut franchir un peu les obstacles et les obstacles avant même de pouvoir vraiment les utiliser.

Si votre programme trouve systématiquement des endroits où vous devez renvoyer plusieurs valeurs ou échanger plusieurs valeurs, cela peut valoir la peine de choisir l’itinéraire du tuple, mais sinon, il est parfois plus simple de faire les choses de manière classique.

En règle générale, tout le monde n’a pas déjà installé Boost, et je n’aurais certainement pas à le télécharger et à configurer mes répertoires d’inclusion pour qu’il fonctionne uniquement pour ses installations de tuple. Je pense que vous constaterez que les personnes qui utilisent déjà Boost sont plus susceptibles de trouver des utilisations de tuple dans leurs programmes que les utilisateurs non-Boost, et que les migrants d’autres langues (Python en vient à l’esprit) en C ++ que d’explorer les méthodes d’ajout du support de tuple.

Vous les voyez rarement car un code bien conçu n’en a généralement pas besoin – il n’y a pas beaucoup de cas dans la nature où l’utilisation d’une structure anonyme est supérieure à l’utilisation d’une structure nommée. Comme tout ce que représente un tuple est une structure anonyme, la plupart des codeurs, dans la plupart des cas, ne font qu’accompagner la réalité.

Disons que nous avons une fonction “f” où un retour de tuple pourrait avoir un sens. En règle générale, ces fonctions sont généralement suffisamment compliquées pour qu’elles puissent échouer.

Si “f” peut échouer, vous avez besoin d’un retour d’état – après tout, vous ne voulez pas que les appelants inspectent chaque paramètre pour détecter une défaillance. “f” rentre probablement dans le schéma:

 struct ReturnInts ( int y,z; } bool f(int x, ReturnInts& vals); int x = 0; ReturnInts vals; if(!f(x, vals)) { ..report error.. ..error handling/return... } 

Ce n’est pas joli, mais regardez à quel point l’alternative est laide. Notez que j’ai toujours besoin d’une valeur d’état, mais le code n’est plus lisible et pas plus court. Il est probablement plus lent aussi, puisque je supporte le coût d’une copie avec le tuple.

 std::tuple f(int x); int x = 0; std::tuple result = f(x); // or "auto result = f(x)" if(!result.get<2>()) { ... report error, error handling ... } 

Un autre inconvénient important est caché ici – avec “ReturnInts” Je peux append le retour de “f” en modifiant “ReturnInts” SANS MODIFIER L’INTERFACE DE “f”. La solution de tuple n’offre pas cette fonctionnalité critique, ce qui en fait la réponse inférieure pour tout code de bibliothèque.

En tant que magasin de données, std::tuple a les pires caractéristiques à la fois d’une struct et d’un tableau; tous les access sont basés sur la nième position, mais on ne peut pas parcourir un tuple utilisant une boucle for .

Donc, si les éléments dans le tuple sont conceptuellement un tableau, je vais utiliser un tableau et si les éléments ne sont pas conceptuellement un tableau, une structure (qui a des éléments nommés) est plus maintenable. ( a.lastname est plus explicatif que std::get<1>(a) ).

Cela laisse la transformation mentionnée par le PO comme la seule solution viable pour les tuples.

J’ai l’impression que beaucoup utilisent Boost.Any et Boost.Variant (avec un peu d’ingénierie) au lieu de Boost.Tuple.