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

124

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

Les tuples vous permettent de faire toutes sortes de choses intéressantes comme celle-ci:

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

C'est certainement mieux que ça:

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

Bien sûr, vous pouvez toujours faire ceci:

swap(a,b);

Mais que faire si vous souhaitez 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 renvoi de plusieurs variables à partir d'une fonction, ce qui est probablement un cas beaucoup plus courant que l'échange de valeurs. Utiliser des références pour renvoyer des valeurs n'est certainement pas très élégant.

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 simplement que les gens n'y sont pas habitués? Est-ce une bonne idée d'utiliser des tuples?

Zifre
la source
17
+1 pour un truc intelligent d'échange de tuple :)
kizzx2
10
a = a ^ b; b = a ^ b; a = a ^ b;
Gerardo Marset
3
Les tuples IMO sont pratiques dans les langages de frappe faibles ou les langues dans lesquelles ils sont des structures natives. Par exemple, en Python ou PHP, ils facilitent simplement la vie, tandis qu'en C ++, il y a trop de frappe (pour le construire à partir d'un modèle) et trop peu d'avantages.
doc
4
Un commentaire au PO: je pense que la réponse actuellement acceptée est déjà obsolète au point d'être factuellement erronée. Vous voudrez peut-être reconsidérer le choix de la réponse acceptée.
ulidtko
8
@GerardoMarset Êtes-vous sérieux?
thesaint

Réponses:

43

Parce que ce n'est pas encore standard. Tout ce qui n'est pas standard a un obstacle beaucoup plus élevé. Les morceaux de Boost sont devenus populaires parce que les programmeurs les réclamaient. (hash_map me vient à l'esprit). Mais bien que le tuple soit pratique, ce n'est pas une victoire si écrasante et claire que les gens s'en soucient.

Alan De Smet
la source
1
Les gens semblent utiliser d'autres parties de Boost comme des fous. Bien que les cartes de hachage soient certainement beaucoup plus utiles en général que les tuples.
Zifre
Je ne connais pas les détails de ce que vous voyez, mais je suppose que les parties que les gens utilisent comme des fous sont des fonctionnalités qu'ils voulaient vraiment, vraiment. Ainsi (encore une fois, en devinant) la popularité de la carte de hachage, du pointeur compté, etc. Le tuple est pratique, mais ce n'est pas quelque chose qui saute pour combler un trou. Le gain n'est pas évident. À quelle fréquence devez-vous faire pivoter exactement N objets? (Par opposition à la nécessité de faire tourner des vecteurs arbitrairement longs). Et les gens sont habitués soit à passer des valeurs de retour par référence, soit à renvoyer de petites classes ou structures.
Alan De Smet
20
Il fait actuellement partie de la norme C ++ 11 maintenant: en.cppreference.com/w/cpp/utility/tuple
Roman Susi
124

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

À titre d'exemple sans boost: combien de personnes utilisent les fonctionnalités présentes dans <algorithm>?

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

Trey Jackson
la source
18
-1 de ma part parce que les programmeurs C ++ ne sont pas aussi stupides que cette réponse les fait sonner.
user541686
5
@Mehrdad Après avoir parcouru beaucoup de code C ++, à la fois commercial et non, en lisant des tonnes de matériel C ++, je pense qu'il est assez sûr de dire qu'une très grande partie des développeurs "C ++" sont juste des développeurs C qui ne pouvaient pas obtenir un Compilateur C. Les modèles, par exemple, sont presque entièrement absents de la plupart des matériaux (quelque chose que j'ai beaucoup appris à aimer). Les piratages de macros étranges sont courants et les espaces de noms sont gravement sous-utilisés.
Plus clair
5
Réponse absurde. Si on en a besoin, ils les rattraperont. Ils ne sont pas nécessaires; donc ils ne sont pas utilisés. Dire qu'ils ne sont pas utilisés parce qu'ils ne sont pas faciles à comprendre est mauvais.
Michael Chourdakis
9
Commentaire @Michael Nonsense. Rien n'est nécessaire dans la programmation une fois que vous avez un sous-ensemble complet de Turing d'un langage. Le manque d'utilisation n'implique certainement pas que tout le monde comprend les constructions C ++ de niveau supérieur et choisit de ne pas les utiliser.
Trey Jackson
4
Tbh Je n'ai JAMAIS eu besoin de std :: tuple en dehors de la métaprogrammation de modèles variadiques. Il n'y avait aucun moment dans la vie où je me suis assis avec un visage triste en pensant "Si seulement j'avais ces tuples". En fait, quand je regarde les tuples, je pense "Pourquoi diable quelqu'un de sain d'esprit aurait-il besoin d'eux (je ne me considérerais pas sain d'esprit)". Les gens en dehors de la méta-programmation semblent les utiliser comme des «structures anonymes», ce qui est si laid et indique une forte absence de qualité de code et de maintenabilité dans leur tête.
thesaint
23

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

Considérer:

typedef boost::tuple<MyClass1,MyClass2,MyClass3> MyTuple;

Donc, si vous voulez faire un usage intensif des tuples, soit vous obtenez des typedefs de tuple partout, soit vous obtenez des noms de type extrêmement longs partout. J'aime les tuples. Je les utilise quand c'est nécessaire. Mais il est généralement limité à quelques situations, comme un index à N éléments ou lors de l'utilisation de multi-cartes pour lier les paires d'itérateurs de plage. Et c'est généralement dans une portée très limitée.

Tout cela a l'air très laid et hacky par rapport à quelque chose comme Haskell ou Python. Lorsque C ++ 0x arrive ici et que nous obtenons les tuples de mot-clé 'auto', les tuples commenceront à paraître beaucoup plus attrayants.

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

user21714
la source
La plupart des gens feront "en utilisant le boost d'espace de noms"; et pas besoin de taper boost ::. Je ne pense pas que tuple soit un problème. Cela dit, je pense que vous avez un point. auto pourrait inciter beaucoup plus de gens à utiliser les tuples.
Zifre
2
@Zifre: le problème est que vous ne devriez pas faire "utiliser l'espace de noms X" dans un fichier d'en-tête car cela force la pollution des espaces de noms et subvertit les espaces de noms.
Mr Fooz
1
Ah, oui, j'oublie les en-têtes. Mais à l'intérieur du code du programme, vous n'avez pas à vous en soucier. Et une fois que nous avons C ++ 0x, nous pouvons utiliser auto, ce qui devrait éliminer une grande partie de la saisie.
Zifre
19
Est ce juste moi? Je ne pense pas que sauver la saisie des 7 caractères de "boost ::" soit ce à quoi il faisait référence, mais plutôt les 33 autres caractères . C'est un sacré nombre de types de noms de classes, surtout si ceux-ci sont également limités à l'espace de noms. Prenez boost :: tuple <std :: string, std :: set <std :: string>, std :: vector <My :: Scoped :: LongishTypeName>> comme un exemple ridicule.
Ogre Psalm33
10

Pour moi, c'est une habitude, haut la main: les tuples ne résolvent pas de nouveaux problèmes pour moi, juste quelques-uns que je peux déjà gérer très bien. L'échange de valeurs semble toujours plus facile à l'ancienne - et, plus important encore, je ne pense pas vraiment à la façon d'é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 structs.

ojrac
la source
4
'Je ne réfléchis pas vraiment à la manière d'échanger «mieux». »- Quand j'écris du code, j'écris des bogues. Réduire la complexité du code réduit le nombre de bogues que j'écris. Je déteste créer les mêmes bugs encore et encore. Oui, je réfléchis à la façon de mieux <strike> swap </> code . Moins de pièces mobiles (LOC, variables temp, identifiants à mal saisir), code plus lisible; Bon code.
sehe
Se mettre d'accord. Une classe encapsulée dans un pointeur automatique ou un pointeur intelligent est une sauvegarde de type. J'ai utilisé des tuples une fois, puis réécrit le code en utilisant des classes. retValue.state est plus clair que retValue.get <0> ().
Valentin Heinitz
1
@sehe: Ecrire du code mieux et plus lisible est aussi mon objectif. Ajouter plus de types de syntaxe a un coût, et je ne crois pas qu'une «meilleure permutation» justifie la surcharge mentale de penser à encore plus de types de syntaxe pour chaque ligne de code que vous lisez.
ojrac
8

Mais que faire si vous souhaitez faire pivoter trois valeurs?

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

OK, donc avec 4 valeurs etc, le n-uplet devient finalement moins de code que n-1 swaps. Et avec l'échange par défaut, cela fait 6 affectations au lieu des 4 que vous auriez si vous implémentiez vous-même un modèle à trois cycles, même si j'espère que le compilateur résoudra cela pour les types simples.

Vous pouvez proposer des scénarios où les swaps sont peu maniables ou inappropriés, par exemple:

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

est un peu difficile à décompresser.

Le point est, cependant, qu'il existe des moyens connus de traiter les situations les plus courantes pour lesquelles les tuples sont bons, et donc pas une grande urgence de prendre des tuples. Si rien d'autre, je ne suis pas convaincu 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 persuader que les tuples sont une bonne idée pour les types "grands", 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 du tout 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 paramètres d'entrée et de sortie, et ne pourraient probablement pas être persuadées avec une batte de baseball d'utiliser des tuples. Pas de prise en compte du goût.

Steve Jessop
la source
1
Vous ne voudriez certainement pas échanger des vecteurs avec des tuples. Je pense que l'échange de trois éléments est certainement plus clair avec les tuples qu'avec deux swaps. En ce qui concerne les valeurs de retour multiples, les paramètres out sont mauvais, les structures sont un type supplémentaire et il y a certainement des cas où plusieurs valeurs de retour sont nécessaires.
Zifre
sachez pourquoi vous utilisez des tuples (et je sais pourquoi je les utiliserais si l'occasion se présentait, même si je ne pense pas que cela ait jamais été le cas). Je devine pourquoi les autres ne les utilisent pas même s'ils en sont conscients. par exemple parce qu'ils ne sont pas d'accord avec "les paramètres de sortie sont mauvais" ...
Steve Jessop
Savez-vous si nous pouvons remplacer "tie (a, b, c) = make_tuple (b, c, a);" par "tie (a, b, c) = tie (b, c, a);" ?
Rexxar
2
Une cravate (enfin, un niveau techniquement) est un tuple fait avec des références non const. Je ne peux pas trouver de documentation boost qui dit ce que garantit l'opérateur = et le constructeur de copie pour tie / tuple make lorsque certaines des références impliquées ont le même référand. Mais c'est ce que vous devez savoir. Une implémentation naïve de operator = pourrait clairement aller très mal ...
Steve Jessop
1
@Steve: Et comme les liens consistent uniquement à empêcher les copies (ils devraient fonctionner pour les types non copiables; notez que le LHS pourrait être tout autre chose) en fait, tout devrait aller très mal (pensez aux objets de classe non POD). Imaginez simplement comment vous écririez la même logique sans utiliser de temps.
sehe
7

Comme beaucoup de gens l'ont souligné, les tuples ne sont tout simplement pas aussi utiles que les autres fonctionnalités.

  1. Les gadgets d'échange et de rotation ne sont que des gadgets. Ils sont totalement déroutants pour ceux qui ne les ont jamais vus auparavant, et comme c'est à peu près tout le monde, ces gadgets ne sont que de mauvaises pratiques en génie logiciel.

  2. 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 renvoyées, si elles sont mutuellement convertibles, et ne pas être plus sage.

igorlord
la source
6

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

Brian Neal
la source
3
Beaucoup de gens utilisent Boost. Ces personnes pourraient également utiliser des tuples.
Zifre
3
Vous avez demandé pourquoi les gens ne les utilisent pas, et j'ai donné une réponse.
Brian Neal
2
À l'électeur vers le bas: il se trouve que je travaille dans un endroit où il est politiquement impossible d'utiliser boost, et même à ce jour, la chaîne d'outils du compilateur que nous utilisons (pour un système embarqué) ne prend pas en charge TR1 / C ++ 11.
Brian Neal
5

Lors de l'utilisation de C ++ sur des systèmes embarqués, l'extraction des bibliothèques Boost devient complexe. Ils se couplent les uns aux autres, donc la taille de la bibliothèque augmente. Vous retournez des structures de données ou utilisez le passage de paramètres au lieu de tuples. Lors du retour de tuples en Python, la structure de données est dans l'ordre et le type des valeurs renvoyées, ce n'est tout simplement pas explicite.

DanM
la source
5

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. Puisque tout ce qu'un tuple représente vraiment est une structure anonyme, la plupart des codeurs dans la plupart des situations vont simplement avec la vraie chose.

Supposons que nous ayons 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 doivent inspecter chaque paramètre pour détecter un échec. "f" s'inscrit probablement dans le motif:

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 de statut, mais le code n'est ni plus lisible ni plus court. C'est probablement plus lent aussi, puisque j'encourt le coût d'une copie avec le tuple.

std::tuple<int, int, bool> f(int x);
int x = 0;
std::tuple<int, int, bool> 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 ajouter le retour de "f" en modifiant "ReturnInts" SANS ALTÉRER L'INTERFACE de "f". La solution tuple n'offre pas cette fonctionnalité critique, ce qui en fait la réponse inférieure pour tout code de bibliothèque.

Zack Yezek
la source
1
Les exceptions rendent cette interface beaucoup plus propre.
David Stone
Pour être juste (et extrêmement tardif pour la fête), vous pouvez faciliter la lisibilité en définissant using std::tuple;et en utilisant simplement tuplele code.
Alrekr
2
L'utilisation tuplerend le code moins lisible, pas plus. La plupart des codes de nos jours contiennent un très grand nombre de symboles - voir std::tupleclairement ce que c'est.
Tom Swirly
3

Les tuples peuvent certainement être utiles, mais comme mentionné, il y a un peu de surcharge et un obstacle ou deux que vous devez franchir avant de pouvoir même les utiliser vraiment.

Si votre programme trouve systématiquement des endroits où vous devez renvoyer plusieurs valeurs ou échanger plusieurs valeurs, cela peut valoir la peine d'emprunter la route du tuple, mais sinon, il est parfois plus facile de faire les choses de manière classique.

De manière générale, Boost n'est pas déjà installé sur tout le monde, et je n'aurais certainement pas à me soucier de le télécharger et de configurer mes répertoires d'inclusion pour qu'il fonctionne uniquement pour ses fonctionnalités 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 les migrants d'autres langues (Python vient à l'esprit) sont plus susceptibles d'être simplement contrariés par le manque de tuples en C ++ que d'explorer des méthodes d'ajout de support de tuple.

Zac
la source
1

En tant que magasin de données std::tuple a les pires caractéristiques de a structet d'un tableau; tout accès est basé sur la nième position mais on ne peut pas parcourir tupleune forboucle en utilisant une boucle.

Donc, si les éléments du tuple sont conceptuellement un tableau, j'utiliserai un tableau et si les éléments ne sont pas conceptuellement un tableau, une structure (qui a des éléments nommés) est plus facile à maintenir. ( a.lastnameest plus explicatif que std::get<1>(a)).

Cela laisse la transformation mentionnée par l'OP comme le seul cas d'utilisation viable pour les tuples.

Doron
la source
0

J'ai le sentiment que beaucoup utilisent Boost.Any et Boost.Variant (avec un peu d'ingénierie) au lieu de Boost.Tuple.

dirkgently
la source
Pourquoi échangeriez-vous un typage statique efficace contre quelque chose comme ça?
Zifre
Boost.Variant est totalement sécurisé.
user21714
1
Oups, oui, il est de type sécurisé, mais il effectue une saisie à l'exécution.
Zifre
5
Je ne vois pas comment Tuple pourrait être remplacé Any / Variant. Ils ne font pas la même chose.
Mankarse
1
@Zifre Je ne peux pas parler au nom de l'auteur, mais je pense que l'implication ici est de les utiliser avec d'autres types de conteneurs.
Tim Seguine