Ceci fait suite à ma question précédente sur les jolis conteneurs STL , pour lesquels nous avons réussi à développer une solution très élégante et totalement générale.
Dans cette prochaine étape, je voudrais inclure la jolie impression pour std::tuple<Args...>
, en utilisant des modèles variadiques (donc c'est strictement C ++ 11). Car std::pair<S,T>
, je dis simplement
std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p)
{
return o << "(" << p.first << ", " << p.second << ")";
}
Quelle est la construction analogue pour imprimer un tuple?
J'ai essayé divers morceaux de décompression de la pile d'arguments de modèle, de passer des index et d'utiliser SFINAE pour découvrir quand je suis au dernier élément, mais sans succès. Je ne vous charge pas de mon code cassé; la description du problème est, espérons-le, assez simple. Essentiellement, j'aimerais le comportement suivant:
auto a = std::make_tuple(5, "Hello", -0.1);
std::cout << a << std::endl; // prints: (5, "Hello", -0.1)
Points bonus pour avoir inclus le même niveau de généralité (char / wchar_t, séparateurs de paires) que la question précédente!
la source
Réponses:
Ouais, indices ~
namespace aux{ template<std::size_t...> struct seq{}; template<std::size_t N, std::size_t... Is> struct gen_seq : gen_seq<N-1, N-1, Is...>{}; template<std::size_t... Is> struct gen_seq<0, Is...> : seq<Is...>{}; template<class Ch, class Tr, class Tuple, std::size_t... Is> void print_tuple(std::basic_ostream<Ch,Tr>& os, Tuple const& t, seq<Is...>){ using swallow = int[]; (void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get<Is>(t)), 0)...}; } } // aux:: template<class Ch, class Tr, class... Args> auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) -> std::basic_ostream<Ch, Tr>& { os << "("; aux::print_tuple(os, t, aux::gen_seq<sizeof...(Args)>()); return os << ")"; }
Exemple en direct sur Ideone.
Pour les délimiteurs, ajoutez simplement ces spécialisations partielles:
// Delimiters for tuple template<class... Args> struct delimiters<std::tuple<Args...>, char> { static const delimiters_values<char> values; }; template<class... Args> const delimiters_values<char> delimiters<std::tuple<Args...>, char>::values = { "(", ", ", ")" }; template<class... Args> struct delimiters<std::tuple<Args...>, wchar_t> { static const delimiters_values<wchar_t> values; }; template<class... Args> const delimiters_values<wchar_t> delimiters<std::tuple<Args...>, wchar_t>::values = { L"(", L", ", L")" };
et modifiez le
operator<<
et enprint_tuple
conséquence:template<class Ch, class Tr, class... Args> auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) -> std::basic_ostream<Ch, Tr>& { typedef std::tuple<Args...> tuple_t; if(delimiters<tuple_t, Ch>::values.prefix != 0) os << delimiters<tuple_t,char>::values.prefix; print_tuple(os, t, aux::gen_seq<sizeof...(Args)>()); if(delimiters<tuple_t, Ch>::values.postfix != 0) os << delimiters<tuple_t,char>::values.postfix; return os; }
Et
template<class Ch, class Tr, class Tuple, std::size_t... Is> void print_tuple(std::basic_ostream<Ch, Tr>& os, Tuple const& t, seq<Is...>){ using swallow = int[]; char const* delim = delimiters<Tuple, Ch>::values.delimiter; if(!delim) delim = ""; (void)swallow{0, (void(os << (Is == 0? "" : delim) << std::get<Is>(t)), 0)...}; }
la source
TuplePrinter
n'a pas de fichieroperator<<
.class Tuple
pour laoperator<<
surcharge - il serait choisi pour toutes les choses. Il faudrait une contrainte, ce qui implique en quelque sorte le besoin d'une sorte d'arguments variadiques.swallow{(os << get<Is>(t))...};
.Cela fonctionne bien en C ++ 11 (gcc 4.7). Il y a, je suis sûr, quelques pièges que je n'ai pas envisagés 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 renvoient le nombre de types dans le type pack. Il est utilisé pour déterminer l'indice du dernier élément (taille ... (Types) - 1).
template<typename Type, unsigned N, unsigned Last> struct tuple_printer { static void print(std::ostream& out, const Type& value) { out << std::get<N>(value) << ", "; tuple_printer<Type, N + 1, Last>::print(out, value); } }; template<typename Type, unsigned N> struct tuple_printer<Type, N, N> { static void print(std::ostream& out, const Type& value) { out << std::get<N>(value); } }; template<typename... Types> std::ostream& operator<<(std::ostream& out, const std::tuple<Types...>& value) { out << "("; tuple_printer<std::tuple<Types...>, 0, sizeof...(Types) - 1>::print(out, value); out << ")"; return out; }
la source
std::make_tuple()
. mais au moment de l'imprimermain()
, il jette un tas d'erreurs !, Des suggestions sur un moyen plus simple d'imprimer les tuples?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<class TupType, size_t... I> void print(const TupType& _tup, std::index_sequence<I...>) { std::cout << "("; (..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup))); std::cout << ")\n"; } template<class... T> void print (const std::tuple<T...>& _tup) { print(_tup, std::make_index_sequence<sizeof...(T)>()); }
Sorties de démonstration en direct :
donné
auto a = std::make_tuple(5, "Hello", -0.1); print(a);
Explication
Notre pli gauche unaire est de la forme
où
op
dans notre scénario est l'opérateur virgule, etpack
est l'expression contenant notre tuple dans un contexte non développé comme:(..., (std::cout << std::get<I>(myTuple))
Donc, si j'ai un tuple comme ça:
auto myTuple = std::make_tuple(5, "Hello", -0.1);
Et
std::integer_sequence
dont les valeurs sont spécifiées par un modèle non-type (voir code ci-dessus)size_t... I
Puis l'expression
(..., (std::cout << std::get<I>(myTuple))
S'étend en
((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple));
Qui imprimera
Ce qui est dégoûtant, nous devons donc faire plus de supercherie pour ajouter un séparateur virgule à imprimer en premier, à moins que ce ne soit le premier élément.
Pour ce faire, nous modifions la
pack
partie de l'expression de repli à imprimer" ,"
si l'index courantI
n'est pas le premier, d'où la(I == 0? "" : ", ")
partie * :(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));
Et maintenant nous aurons
Ce qui a l'air plus joli (Remarque: je voulais une sortie similaire à celle de cette réponse )
* Remarque: vous pouvez effectuer la séparation par virgule de différentes manières que celles avec lesquelles j'ai fini. J'ai d'abord ajouté des virgules conditionnellement après au lieu d' avant en testant contre
std::tuple_size<TupType>::value - 1
, mais c'était trop long, donc j'ai testé à la placesizeof...(I) - 1
, mais à la fin j'ai copié Xeo et nous avons fini avec ce que j'ai.la source
if constexpr
pour le cas de base.Je suis surpris que l'implémentation sur cppreference n'ait pas déjà été publiée ici, alors je vais le faire pour la postérité. Il est caché dans la documentation 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 <iostream> #include <tuple> #include <string> // helper function to print a tuple of any size template<class Tuple, std::size_t N> struct TuplePrinter { static void print(const Tuple& t) { TuplePrinter<Tuple, N-1>::print(t); std::cout << ", " << std::get<N-1>(t); } }; template<class Tuple> struct TuplePrinter<Tuple, 1> { static void print(const Tuple& t) { std::cout << std::get<0>(t); } }; template<class... Args> void print(const std::tuple<Args...>& t) { std::cout << "("; TuplePrinter<decltype(t), sizeof...(Args)>::print(t); std::cout << ")\n"; } // end helper function
Et un test:
int main() { std::tuple<int, std::string, float> 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); }
Production:
Démo en direct
la source
Basé sur le code AndyG, pour C ++ 17
#include <iostream> #include <tuple> template<class TupType, size_t... I> std::ostream& tuple_print(std::ostream& os, const TupType& _tup, std::index_sequence<I...>) { os << "("; (..., (os << (I == 0 ? "" : ", ") << std::get<I>(_tup))); os << ")"; return os; } template<class... T> std::ostream& operator<< (std::ostream& os, const std::tuple<T...>& _tup) { return tuple_print(os, _tup, std::make_index_sequence<sizeof...(T)>()); } int main() { std::cout << "deep tuple: " << std::make_tuple("Hello", 0.1, std::make_tuple(1,2,3,"four",5.5), 'Z') << std::endl; return 0; }
avec sortie:
deep tuple: (Hello, 0.1, (1, 2, 3, four, 5.5), Z)
la source
Basé sur l'exemple du langage de programmation C ++ de Bjarne Stroustrup, page 817 :
#include <tuple> #include <iostream> #include <string> #include <type_traits> template<size_t N> struct print_tuple{ template<typename... T>static typename std::enable_if<(N<sizeof...(T))>::type print(std::ostream& os, const std::tuple<T...>& t) { char quote = (std::is_convertible<decltype(std::get<N>(t)), std::string>::value) ? '"' : 0; os << ", " << quote << std::get<N>(t) << quote; print_tuple<N+1>::print(os,t); } template<typename... T>static typename std::enable_if<!(N<sizeof...(T))>::type print(std::ostream&, const std::tuple<T...>&) { } }; std::ostream& operator<< (std::ostream& os, const std::tuple<>&) { return os << "()"; } template<typename T0, typename ...T> std::ostream& operator<<(std::ostream& os, const std::tuple<T0, T...>& t){ char quote = (std::is_convertible<T0, std::string>::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<int,double,std::string> c(1,1.2,"Tail!"); std::cout << a << std::endl; std::cout << b << std::endl; std::cout << c << std::endl; }
Production:
() ("One meatball") (1, 1.2, "Tail!")
la source
En s'appuyant sur
std::apply
(C ++ 17), nous pouvons supprimer lestd::index_sequence
et définir une seule fonction:#include <tuple> #include <iostream> template<class Ch, class Tr, class... Args> auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) { std::apply([&os](auto&&... args) {((os << args << " "), ...);}, t); return os; }
Ou, légèrement embelli à l'aide d'un stringstream:
#include <tuple> #include <iostream> #include <sstream> template<class Ch, class Tr, class... Args> auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) { std::basic_stringstream<Ch, Tr> ss; ss << "[ "; std::apply([&ss](auto&&... args) {((ss << args << ", "), ...);}, t); ss.seekp(-2, ss.cur); ss << " ]"; return os << ss.str(); }
la source
Un autre, similaire à celui de @Tony Olsson, incluant une spécialisation pour le tuple vide, comme suggéré par @Kerrek SB.
#include <tuple> #include <iostream> template<class Ch, class Tr, size_t I, typename... TS> struct tuple_printer { static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t) { tuple_printer<Ch, Tr, I-1, TS...>::print(out, t); if (I < sizeof...(TS)) out << ","; out << std::get<I>(t); } }; template<class Ch, class Tr, typename... TS> struct tuple_printer<Ch, Tr, 0, TS...> { static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t) { out << std::get<0>(t); } }; template<class Ch, class Tr, typename... TS> struct tuple_printer<Ch, Tr, -1, TS...> { static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t) {} }; template<class Ch, class Tr, typename... TS> std::ostream & operator<<(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t) { out << "("; tuple_printer<Ch, Tr, sizeof...(TS) - 1, TS...>::print(out, t); return out << ")"; }
la source
J'aime la réponse de DarioP, mais stringstream utilise le tas. Cela peut être évité:
template <class... Args> std::ostream& operator<<(std::ostream& os, std::tuple<Args...> const& t) { os << "("; bool first = true; std::apply([&os, &first](auto&&... args) { auto print = [&] (auto&& val) { if (!first) os << ","; (os << " " << val); first = false; }; (print(args), ...); }, t); os << " )"; return os; }
la source
Une chose que je n'aime pas dans les réponses précédentes qui utilisent des expressions de pliage est qu'elles utilisent des séquences d'index ou des indicateurs pour garder une trace du premier élément, ce qui supprime une grande partie des avantages des belles expressions de pliage propre.
Voici un exemple qui ne nécessite pas d'indexation, mais qui aboutit à un résultat similaire. (Pas aussi sophistiqué que certains des autres, mais d'autres pourraient être ajoutés.)
La technique consiste à utiliser ce que le pli vous donne déjà: un cas particulier pour un élément. Ie, un pli d'élément se développe simplement
elem[0]
, puis 2 éléments sontelem[0] + elem[1]
, où se+
trouve une opération. Ce que nous voulons, c'est qu'un élément écrive uniquement cet élément dans le flux, et pour plus d'éléments, fasse de même, mais joint chacun d'eux avec une écriture supplémentaire de ",". Donc, en mappant cela sur le pli c ++, nous voulons que chaque élément soit l'action d'écrire un objet dans le flux. Nous voulons que notre+
opération soit entrecoupée de deux écritures avec un "," write. Commencez par transformer notre séquence de tuple en une séquence d'actions d'écriture,CommaJoiner
je l'ai appelée, puis pour cette action, ajoutez unoperator+
pour joindre deux actions comme nous le souhaitons, en ajoutant un "," entre les deux:#include <tuple> #include <iostream> template <typename T> struct CommaJoiner { T thunk; explicit CommaJoiner(const T& t) : thunk(t) {} template <typename S> auto operator+(CommaJoiner<S> const& b) const { auto joinedThunk = [a=this->thunk, b=b.thunk] (std::ostream& os) { a(os); os << ", "; b(os); }; return CommaJoiner<decltype(joinedThunk)>{joinedThunk}; } void operator()(std::ostream& os) const { thunk(os); } }; template <typename ...Ts> std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> tup) { std::apply([&](auto ...ts) { return (... + CommaJoiner{[=](auto&os) {os << ts;}});}, tup)(os); return os; } int main() { auto tup = std::make_tuple(1, 2.0, "Hello"); std::cout << tup << std::endl; }
Un rapide coup d'œil à godbolt suggère que cela se compile assez bien aussi, tous les appels de thunks étant aplatis.
Cependant, cela nécessitera une deuxième surcharge pour traiter un tuple vide.
la source