Pretty-print std :: tuple

Ceci fait suite à ma précédente question sur les conteneurs STL à impression jolie , pour laquelle nous avons réussi à développer une solution très élégante et entièrement générale.


Dans cette prochaine étape, je souhaiterais inclure la jolie impression pour std::tuple , en utilisant des templates variadic (donc c’est ssortingctement C ++ 11). Pour std::pair , je dis simplement

 std::ostream & operator<<(std::ostream & o, const std::pair & p) { return o << "(" << p.first << ", " << p.second << ")"; } 

Quelle est la construction analogue pour imprimer un tuple?

J’ai essayé différents éléments de la stack de l’argumentation, en passant des indices et en utilisant SFINAE pour découvrir le dernier élément, mais sans succès. Je ne vais pas te charger de mon code cassé; La description du problème est assez simple. Essentiellement, je voudrais le comportement suivant:

 auto a = std::make_tuple(5, "Hello", -0.1); std::cout << a << std::endl; // prints: (5, "Hello", -0.1) 

Des points bonus pour inclure le même niveau de généralité (char / wchar_t, délimiteurs de paires) que la question précédente!

Yay, indices ~

 namespace aux{ template struct seq{}; template struct gen_seq : gen_seq{}; template struct gen_seq<0, Is...> : seq{}; template void print_tuple(std::basic_ostream& os, Tuple const& t, seq){ using swallow = int[]; (void)swallow{0, (void(os < < (Is == 0? "" : ", ") << std::get(t)), 0)...}; } } // aux:: template auto operator< <(std::basic_ostream& os, std::tuple const& t) -> std::basic_ostream& { os < < "("; aux::print_tuple(os, t, aux::gen_seq()); return os < < ")"; } 

Exemple en direct sur Ideone.


Pour les délimiteurs, ajoutez simplement ces spécialisations partielles:

 // Delimiters for tuple template struct delimiters, char> { static const delimiters_values values; }; template const delimiters_values delimiters, char>::values = { "(", ", ", ")" }; template struct delimiters, wchar_t> { static const delimiters_values values; }; template const delimiters_values delimiters, wchar_t>::values = { L"(", L", ", L")" }; 

et changez l' operator< < et print_tuple conséquence:

 template auto operator< <(std::basic_ostream& os, std::tuple const& t) -> std::basic_ostream& { typedef std::tuple tuple_t; if(delimiters::values.prefix != 0) os < < delimiters::values.prefix; print_tuple(os, t, aux::gen_seq()); if(delimiters::values.postfix != 0) os < < delimiters::values.postfix; return os; } 

Et

 template void print_tuple(std::basic_ostream& os, Tuple const& t, seq){ using swallow = int[]; char const* delim = delimiters::values.delimiter; if(!delim) delim = ""; (void)swallow{0, (void(os < < (Is == 0? "" : delim) << std::get(t)), 0)...}; } 

J’ai bien fonctionné en C ++ 11 (gcc 4.7). Je suis sûr de certains pièges que je n’ai pas pris en compte mais je pense que le code est facile à lire et pas compliqué. La seule chose qui peut être étrange est la structure “guard” tuple_printer qui garantit que nous nous terminons lorsque le dernier élément est atteint. L’autre chose étrange peut être sizeof … (Types) qui renvoie le nombre de types dans Type type pack. Il est utilisé pour déterminer l’index du dernier élément (taille … (Types) – 1).

 template struct tuple_printer { static void print(std::ostream& out, const Type& value) { out < < std::get(value) < < ", "; tuple_printer::print(out, value); } }; template struct tuple_printer { static void print(std::ostream& out, const Type& value) { out < < std::get(value); } }; template std::ostream& operator< <(std::ostream& out, const std::tuple& value) { out < < "("; tuple_printer, 0, sizeof...(Types) - 1>::print(out, value); out < < ")"; return out; } 

Je suis surpris que l’implémentation sur cppreference n’ait pas encore été postée ici, alors je le ferai pour la postérité. Il est caché dans le doc pour std::tuple_cat donc ce n’est pas facile à trouver. Il utilise une structure de garde comme certaines des autres solutions ici, mais je pense que la leur est finalement plus simple et plus facile à suivre.

 #include  #include  #include  // helper function to print a tuple of any size template struct TuplePrinter { static void print(const Tuple& t) { TuplePrinter::print(t); std::cout < < ", " << std::get(t); } }; template struct TuplePrinter { static void print(const Tuple& t) { std::cout < < std::get<0>(t); } }; template void print(const std::tuple& t) { std::cout < < "("; TuplePrinter::print(t); std::cout < < ")\n"; } // end helper function 

Et un test:

 int main() { std::tuple t1(10, "Test", 3.14); int n = 7; auto t2 = std::tuple_cat(t1, std::make_pair("Foo", "bar"), t1, std::tie(n)); n = 10; print(t2); } 

Sortie:

(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)

Démo en direct

En C ++ 17, nous pouvons accomplir cela avec un peu moins de code en tirant parti des expressions Fold , en particulier un pli gauche unaire:

 template void print(const TupType& _tup, std::index_sequence) { std::cout < < "("; (..., (std::cout << (I == 0? "" : ", ") << std::get(_tup))); std::cout < < ")\n"; } template void print (const std::tuple& _tup) { print(_tup, std::make_index_sequence()); } 

Sorties de démonstration en direct :

(5, Bonjour, -0.1)

donné

 auto a = std::make_tuple(5, "Hello", -0.1); print(a); 

Explication

Notre pli gauche unaire est de la forme

 ... op pack 

op dans notre scénario est l’opérateur virgule, et pack est l’expression contenant notre tuple dans un contexte non développé comme:

 (..., (std::cout < < std::get(myTuple)) 

Donc, si j’ai un tuple comme ça:

 auto myTuple = std::make_tuple(5, "Hello", -0.1); 

Et une std::integer_sequence dont les valeurs sont spécifiées par un template non-typé (voir le code ci-dessus)

 size_t... I 

Alors l’expression

 (..., (std::cout < < std::get(myTuple)) 

Se développe dans

 ((std::cout < < std::get<0>(myTuple)), (std::cout < < std::get<1>(myTuple))), (std::cout < < std::get<2>(myTuple)); 

Qui imprimera

5Hello-0.1

Ce qui est brut, nous avons donc besoin de faire plus de ruses pour append un séparateur de virgules à imprimer en premier, à moins que ce soit le premier élément.

Pour ce faire, nous modifions la partie pack de l’expression du pli à imprimer " ," si l’index courant I n’est pas le premier, donc la partie (I == 0? "" : ", ") * :

 (..., (std::cout < < (I == 0? "" : ", ") << std::get(_tup))); 

Et maintenant nous allons avoir

5, bonjour, -0,1

Ce qui est plus joli (Note: je voulais un résultat similaire à cette réponse )

* Remarque: Vous pouvez faire la séparation des virgules de différentes manières que ce à quoi je me suis retrouvé. J’ai initialement ajouté des virgules conditionnellement après au lieu de avant en testant sur std::tuple_size::value - 1 , mais c’était trop long, alors j’ai plutôt testé sizeof...(I) - 1 , mais finalement J’ai copié Xeo et nous avons fini avec ce que j’ai.

Basé sur l’exemple du langage de programmation C ++ Par Bjarne Stroustrup, page 817 :

 #include  #include  #include  #include  template struct print_tuple{ templatestatic typename std::enable_if< (N::type print(std::ostream& os, const std::tuple& t) { char quote = (std::is_convertible(t)), std::ssortingng>::value) ? '"' : 0; os < < ", " << quote << std::get(t) < < quote; print_tuple::print(os,t); } templatestatic typename std::enable_if< !(N::type print(std::ostream&, const std::tuple&) { } }; std::ostream& operator< < (std::ostream& os, const std::tuple<>&) { return os < < "()"; } template std::ostream& operator< <(std::ostream& os, const std::tuple& t){ char quote = (std::is_convertible::value) ? '"' : 0; os < < '(' << quote << std::get<0>(t) < < quote; print_tuple<1>::print(os,t); return os < < ')'; } int main(){ std::tuple<> a; auto b = std::make_tuple("One meatball"); std::tuple c(1,1.2,"Tail!"); std::cout < < a << std::endl; std::cout << b << std::endl; std::cout << c << std::endl; } 

Sortie:

 () ("One meatball") (1, 1.2, "Tail!") 

Un autre, similaire à celui de @Tony Olsson, incluant une spécialisation pour le tuple vide, comme suggéré par @Kerrek SB.

 #include  #include  template struct tuple_printer { static void print(std::basic_ostream & out, const std::tuple & t) { tuple_printer::print(out, t); if (I < sizeof...(TS)) out << ","; out << std::get(t); } }; template struct tuple_printer { static void print(std::basic_ostream & out, const std::tuple & t) { out < < std::get<0>(t); } }; template struct tuple_printer { static void print(std::basic_ostream & out, const std::tuple & t) {} }; template std::ostream & operator< <(std::basic_ostream & out, const std::tuple & t) { out < < "("; tuple_printer::print(out, t); return out < < ")"; }