2010-08-07 3 views
11

tuple dans boost et TR1/C++ 0x fournit une méthode pratique (pour l'auteur de la fonction) pour retourner deux valeurs d'une fonction - mais il semble dommage une caractéristique majeure de la langue de l'appelant: la possibilité d'utiliser simplement la fonction d'initialiser une variable:Comment initialiser avec plusieurs valeurs de retour en C++ (0x)

T happy(); 
const auto meaningful_name(happy()); // RVO means no excess copies 

mais pour:

tuple<T,U> sad(); 

nous doivent soit renoncer à la possibilité de choisir un nom significatif pour nos valeurs de retour, et utilisez get<n>() partout:

const auto two_unrelated_things(sad()); 

ou faire un temporaire:

const auto unwanted_named_temporary(sad()); 
const auto one_name(get<0>(unwanted_named_temporary)); 
const auto two_name(get<1>(unwanted_named_temporary)); 

ou passer d'initialisation à l'affectation, qui ne fonctionne que lorsque les types sont cessibles, et les pauses auto:

tuple_element<0, decltype(sad())>::type one_mutable; // there might be a less 
tuple_element<1, decltype(sad())>::type two_mutable; // verbose way 
tie(one_mutable,two_mutable) = sad(); 

ou faire quelque chose contre nature à une classe locale:

const struct ugh { 
    ugh(decltype(sad()) rhs) : one_name(get<0>(rhs)), two_name(get<1>(rhs)) {} 
    const tuple_element<0, decltype(sad())>::type one_name; 
    const tuple_element<1, decltype(sad())>::type two_name; 
} stuff(sad()); // at least we avoid the temporary and get initialization 

Y a-t-il un meilleur moyen? J'utilise des constructions compatibles VC10 ci-dessus, est-ce que tout ce qui est en C++ 0x ou boost serait utile?

Idéalement, il serait:

  • me permettre d'utiliser l'initialisation, non seulement l'affectation
  • laisser le appelant choisir les noms pour les retournés-en variables
  • pas faire des copies supplémentaires
  • travailler pour les variables de pile et les membres de classe
  • peut-être une grande bibliothèque de modèles fou, mais ont une syntaxe saine pour l'appelant et le rédacteur de fonctions
+2

Question intéressante, bien que je ne vois pas comment vous pourriez définir des variables de types différents dans une seule expression. - Je pense que l'option "ou faire une temporaire" pourrait être OK, si vous changez les variables nommées en références (évitant la copie). – UncleBens

+0

Bon point sur les références - Je pense que c'est une solution pour les variables de pile. J'ai essayé de faire la même chose dans une classe: classe C { public: C() sr (triste()), un (get <0> (sr)), deux (se <1> (sr)) {} const T & one; const U & two; privé: tuple sr; } Mais il semble que dans VC10, C est deux pointeurs plus gros que le tuple, pas très gros mais plutôt boiteux - ne serait-il pas légal que le compilateur reconnaisse que les références sont des alias et n'alloue pas d'espace dans le cas pour eux? N'est-ce pas pourquoi les références aux références sont illégales en premier lieu? – BCoates

+0

Avec une classe, si les données sont stockées en tant que tuple, vous pouvez simplement fournir des méthodes d'accès nommées, qui appellent les 'get ' respectifs. Je doute qu'il y ait une solution basée sur un «modèle fou», parce que le langage de base ne semble tout simplement pas soutenir ce que vous demandez. Peut-être que vous pourriez simplement réduire le nombre de caractères que vous devez taper avec des macros ... – UncleBens

Répondre

2
std::tuple<Type1, Type2> returnValue = sad(); 
Type1& first = std::get<0>(returnValue); 
Type2& second = std::get<1>(returnValue); 

Je ne suis pas sûr de ce que votre quatrième moyen de balle, mais qui satisfait tout le reste.

* edit: Basé sur votre commentaire ci-dessus, j'ai compris ce que vous vouliez dire par la quatrième balle.

struct Object { 
    Object(const std::tuple<Type1, Type2>& t) : value(t) { } 
    Type1& First() { return std::get<0>(value); } 
    Type2& second() { return std::get<1>(value); } 
private: 
    std::tuple<Type1, Type2> value; 
} 

Modifier au besoin.

Vous pouvez également ne pas utiliser std::tuple si les valeurs retournées sont si indépendantes que vous devez les séparer afin qu'elles puissent être utilisées raisonnablement. Les gens ont obtenu par des années struct s avec des champs raisonnablement nommés, ou en acceptant les paramètres de référence pour la sortie. En outre, vous semblez être amoureux de auto. Ne sois pas. C'est une grande fonctionnalité, mais c'est pas la façon dont il devrait être utilisé. Votre code va finir illisible si vous ne spécifiez pas les types de temps en temps.

+0

Quand je pense au bit "class member", est-ce même une situation où vous pouvez élider toutes les copies? Je pense que vous finirez par copier la «valeur» au moins une fois dans n'importe quelles circonstances, ce qui nécessitera évidemment de copier ses membres au moins une fois. Donc, vous pourriez aussi bien stocker les valeurs directement et oublier de stocker le tuple. –

+0

Je suis d'accord avec ne pas utiliser 'std :: tuple' quand ce n'est pas approprié - je suis juste inquiet que ça devienne idiomatique et rende le code moins compréhensible et plein de' get (sometuple) ' , on dirait que je m'inquiète trop des copies supplémentaires: 'tie (a, b) = sad()' dans un constructeur élimine toutes les copies des membres de tuple quand sad() fait 'return make_tuple (...)' . Peut-être que la leçon est "const' les membres de données sont un problème". – BCoates

+1

@bcoates: Concernant la mauvaise utilisation et la surutilisation du tuple. La bibliothèque standard est elle-même un peu coupable lorsqu'elle représente des plages comme 'pair . OTOH, boost utilise une alternative supérieure: 'iterator_range ' avec une méthode 'begin()' et 'end()'. Une gamme n'est pas une paire abstraite, «first» et «second» ont une signification concrète! - Je ne pense pas qu'il faille aller très fort dans le code non générique. – UncleBens

Questions connexes