Conteneurs Pretty-print C ++ STL

Veuillez prendre note des mises à jour à la fin de cet article.

Mise à jour: J’ai créé un projet public sur GitHub pour cette bibliothèque!


Je voudrais avoir un modèle unique qui, une fois pour toutes, prend en charge l’impression de tous les conteneurs STL via un operator<< . En pseudo-code, je cherche quelque chose comme ceci:

 template std::ostream & operator<<(std::ostream & o, const C & x) { o << open; // for (typename C::const_iterator i = x.begin(); i != x.end(); i++) /* Old-school */ for (auto i = x.begin(); i != x.end(); i++) { if (i != x.begin()) o << delim; o << *i; } o << close; return o; } 

Maintenant, j’ai vu beaucoup de modèles magiques ici sur SO que je n’aurais jamais cru possible, alors je me demande si quelqu’un peut suggérer quelque chose qui correspondrait à tous les conteneurs C. Peut-être quelque chose qui peut déterminer si quelque chose a l’iterator nécessaire ?

Merci beaucoup!


Mise à jour (et solution)

Après avoir soulevé à nouveau ce problème sur Channel 9 , j’ai obtenu une réponse fantastique de Sven Groot, qui, combinée à un peu de traitement de type SFINAE, semble résoudre le problème de manière complètement générale et nestede. Les délimiteurs peuvent être spécialisés individuellement, un exemple de spécialisation pour std :: set est inclus, ainsi qu’un exemple d’utilisation de délimiteurs personnalisés.

L’aide “wrap_array ()” peut être utilisée pour imprimer des tableaux C bruts. Mise à jour: des paires et des tuples sont disponibles pour l’impression; les délimiteurs par défaut sont des parenthèses.

Le trait de type enable-if requirejs C ++ 0x, mais avec certaines modifications, il devrait être possible d’en faire une version C ++ 98. Les tuples nécessitent des modèles variadiques, donc C ++ 0x.

J’ai demandé à Sven de poster la solution ici pour que je puisse l’accepter, mais en attendant, j’aimerais publier moi-même le code pour référence. ( Mise à jour: Sven a maintenant publié son code ci-dessous, dont j’ai pris la réponse acceptée. Mon propre code utilise des traits de type conteneur, qui fonctionnent pour moi mais peuvent provoquer un comportement inattendu avec des classes non conteneur fournissant des iterators.)

En-tête (prettyprint.h):

 #ifndef H_PRETTY_PRINT #define H_PRETTY_PRINT #include  #include  #include  #include  namespace std { // Pre-declarations of container types so we don't actually have to include the relevant headers if not needed, speeding up compilation time. template class set; } namespace pretty_print { // SFINAE type trait to detect a container based on whether T::const_iterator exists. // (Improvement idea: check also if begin()/end() exist.) template struct is_container_helper { private: template static char test(typename C::const_iterator*); template static int test(...); public: static const bool value = sizeof(test(0)) == sizeof(char); }; // Basic is_container template; specialize to derive from std::true_type for all desired container types template struct is_container : public ::std::integral_constant<bool, is_container_helper::value> { }; // Holds the delimiter values for a specific character type template struct delimiters_values { typedef TChar char_type; const TChar * prefix; const TChar * delimiter; const TChar * postfix; }; // Defines the delimiter values for a specific container and character type template struct delimiters { typedef delimiters_values type; static const type values; }; // Default delimiters template struct delimiters { static const delimiters_values values; }; template const delimiters_values delimiters::values = { "[", ", ", "]" }; template struct delimiters { static const delimiters_values values; }; template const delimiters_values delimiters::values = { L"[", L", ", L"]" }; // Delimiters for set template struct delimiters< ::std::set, char> { static const delimiters_values values; }; template const delimiters_values delimiters< ::std::set, char>::values = { "{", ", ", "}" }; template struct delimiters< ::std::set, wchar_t> { static const delimiters_values values; }; template const delimiters_values delimiters< ::std::set, wchar_t>::values = { L"{", L", ", L"}" }; // Delimiters for pair (reused for tuple, see below) template struct delimiters< ::std::pair, char> { static const delimiters_values values; }; template const delimiters_values delimiters< ::std::pair, char>::values = { "(", ", ", ")" }; template struct delimiters< ::std::pair, wchar_t> { static const delimiters_values values; }; template const delimiters_values delimiters< ::std::pair, wchar_t>::values = { L"(", L", ", L")" }; // Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type. template<typename T, typename TChar = char, typename TCharTraits = ::std::char_traits, typename TDelimiters = delimiters> struct print_container_helper { typedef TChar char_type; typedef TDelimiters delimiters_type; typedef std::basic_ostream & ostream_type; print_container_helper(const T & container) : _container(container) { } inline void operator()(ostream_type & stream) const { if (delimiters_type::values.prefix != NULL) stream << delimiters_type::values.prefix; for (typename T::const_iterator beg = _container.begin(), end = _container.end(), it = beg; it != end; ++it) { if (it != beg && delimiters_type::values.delimiter != NULL) stream << delimiters_type::values.delimiter; stream << *it; } if (delimiters_type::values.postfix != NULL) stream << delimiters_type::values.postfix; } private: const T & _container; }; // Type-erasing helper class for easy use of custom delimiters. // Requires TCharTraits = std::char_traits and TChar = char or wchar_t, and MyDelims needs to be defined for TChar. // Usage: "cout << pretty_print::custom_delims(x)". struct custom_delims_base { virtual ~custom_delims_base() { } virtual ::std::ostream & stream(::std::ostream &) = 0; virtual ::std::wostream & stream(::std::wostream &) = 0; }; template  struct custom_delims_wrapper : public custom_delims_base { custom_delims_wrapper(const T & t) : t(t) { } ::std::ostream & stream(::std::ostream & stream) { return stream << ::pretty_print::print_container_helper<T, char, ::std::char_traits, Delims>(t); } ::std::wostream & stream(::std::wostream & stream) { return stream << ::pretty_print::print_container_helper<T, wchar_t, ::std::char_traits, Delims>(t); } private: const T & t; }; template  struct custom_delims { template  custom_delims(const Container & c) : base(new custom_delims_wrapper(c)) { } ~custom_delims() { delete base; } custom_delims_base * base; }; } // namespace pretty_print template  inline std::basic_ostream & operator<<(std::basic_ostream & stream, const pretty_print::custom_delims & p) { return p.base->stream(stream); } // Template aliases for char and wchar_t delimiters // Enable these if you have comstackr support // // Implement as "template const sdelims::type sdelims<std::set>::values = { ... }." //template using pp_sdelims = pretty_print::delimiters; //template using pp_wsdelims = pretty_print::delimiters; namespace std { // Prints a print_container_helper to the specified stream. template inline basic_ostream & operator<<(basic_ostream & stream, const ::pretty_print::print_container_helper & helper) { helper(stream); return stream; } // Prints a container to the stream using default delimiters template inline typename enable_if< ::pretty_print::is_container::value, basic_ostream&>::type operator<<(basic_ostream & stream, const T & container) { return stream << ::pretty_print::print_container_helper(container); } // Prints a pair to the stream using delimiters from delimiters<std::pair>. template inline basic_ostream & operator<<(basic_ostream & stream, const pair & value) { if (::pretty_print::delimiters<pair, TChar>::values.prefix != NULL) stream << ::pretty_print::delimiters<pair, TChar>::values.prefix; stream << value.first; if (::pretty_print::delimiters<pair, TChar>::values.delimiter != NULL) stream << ::pretty_print::delimiters<pair, TChar>::values.delimiter; stream << value.second; if (::pretty_print::delimiters<pair, TChar>::values.postfix != NULL) stream << ::pretty_print::delimiters<pair, TChar>::values.postfix; return stream; } } // namespace std // Prints a tuple to the stream using delimiters from delimiters<std::pair>. namespace pretty_print { struct tuple_dummy_t { }; // Just if you want special delimiters for tuples. typedef std::pair tuple_dummy_pair; template struct pretty_tuple_helper { static inline void print(::std::basic_ostream & stream, const Tuple & value) { pretty_tuple_helper::print(stream, value); if (delimiters::values.delimiter != NULL) stream << delimiters::values.delimiter; stream << std::get(value); } }; template struct pretty_tuple_helper { static inline void print(::std::basic_ostream & stream, const Tuple & value) { stream << ::std::get(value); } }; } // namespace pretty_print namespace std { template inline basic_ostream & operator<<(basic_ostream & stream, const tuple & value) { if (::pretty_print::delimiters::values.prefix != NULL) stream << ::pretty_print::delimiters::values.prefix; ::pretty_print::pretty_tuple_helper<const tuple &, sizeof...(Args), TChar, TCharTraits>::print(stream, value); if (::pretty_print::delimiters::values.postfix != NULL) stream << ::pretty_print::delimiters::values.postfix; return stream; } } // namespace std // A wrapper for raw C-style arrays. Usage: int arr[] = { 1, 2, 4, 8, 16 }; std::cout << wrap_array(arr) << ... namespace pretty_print { template  struct array_wrapper { typedef const T * const_iterator; typedef T value_type; array_wrapper(const T (& a)[N]) : _array(a) { } inline const_iterator begin() const { return _array; } inline const_iterator end() const { return _array + N; } private: const T * const _array; }; } // namespace pretty_print template  inline pretty_print::array_wrapper pretty_print_array(const T (& a)[N]) { return pretty_print::array_wrapper(a); } #endif 

Exemple d’utilisation:

 #include  #include  #include  #include  #include  #include  #include  #include  #include  #include "prettyprint.h" // Specialization for a particular container template const pretty_print::delimiters_values pretty_print::delimiters<std::vector, char>::values = { "|| ", " : ", " ||" }; // Custom delimiters for one-off use struct MyDel { static const delimiters_values values; }; const delimiters_values MyDel::values = { "" }; int main(int argc, char * argv[]) { std::ssortingng cs; std::unordered_map um; std::map om; std::set ss; std::vector v; std::vector<std::vector> vv; std::vector<std::pair> vp; std::vector vd; v.reserve(argc - 1); vv.reserve(argc - 1); vp.reserve(argc - 1); vd.reserve(argc - 1); std::cout << "Printing pairs." << std::endl; while (--argc) { std::string s(argv[argc]); std::pair p(argc, s); um[argc] = s; om[argc] = s; v.push_back(s); vv.push_back(v); vp.push_back(p); vd.push_back(1./double(i)); ss.insert(s); cs += s; std::cout << " " << p << std::endl; } std::array a{{ 'h', 'e', 'l', 'l', 'o' }}; std::cout << "Vector: " << v << std::endl << "Incremental vector: " << vv << std::endl << "Another vector: " << vd << std::endl << "Pairs: " << vp << std::endl << "Set: " << ss << std::endl << "OMap: " << om << std::endl << "UMap: " << um << std::endl << "String: " << cs << std::endl << "Array: " << a << std::endl ; // Using custom delimiters manually: std::cout << pretty_print::print_container_helper<std::vector, char, std::char_traits, MyDel>(v) << std::endl; // Using custom delimiters with the type-erasing helper class std::cout << pretty_print::custom_delims(v) << std::endl; // Pairs and tuples and arrays: auto a1 = std::make_pair(std::string("Jello"), 9); auto a2 = std::make_tuple(1729); auto a3 = std::make_tuple("Qrgh", a1, 11); auto a4 = std::make_tuple(1729, 2875, std::pair(1.5, "meow")); int arr[] = { 1, 4, 9, 16 }; std::cout << "C array: " << wrap_array(arr) << std::endl << "Pair: " << a1 << std::endl << "1-tuple: " << a2 << std::endl << "n-tuple: " << a3 << std::endl << "n-tuple: " << a4 << std::endl ; } 

Autres idées d’améliorations:

  • Implémentez la sortie pour std::tuple de la même manière que nous l’avons pour std::pair . Mise à jour: Ceci est maintenant une question distincte sur SO ! Upupdate: Cela a maintenant été mis en place grâce à Xeo!
  • Ajoutez des espaces de noms afin que les classes d’assistance ne soient pas perdues dans l’espace de noms global. Terminé
  • Ajouter des alias de modèle (ou quelque chose de similaire) pour faciliter la création de classes de délimiteurs personnalisées, ou peut-être des macros de préprocesseur?

Mises à jour récentes:

  • J’ai supprimé l’iterator de sortie personnalisé en faveur d’une boucle simple pour la fonction d’impression.
  • Tous les détails d’implémentation sont maintenant dans l’espace de noms pretty_print . Seuls les opérateurs de stream globaux et le wrapper pretty_print_array trouvent dans l’espace de noms global.
  • Correction de l’espacement des noms pour que l’ operator<< soit maintenant correctement dans std .

Remarques:

  • La suppression de l’iterator de sortie signifie qu’il n’y a aucun moyen d’utiliser std::copy() pour obtenir une jolie impression. Je pourrais rétablir le joli iterator s’il s’agit d’une fonctionnalité souhaitée, mais le code de Sven ci-dessous a l’implémentation.
  • C’était une décision consciente de concevoir des constantes de compilation plutôt que des constantes d’object. Cela signifie que vous ne pouvez pas fournir de délimiteurs de manière dynamic à l’exécution, mais cela signifie également qu’il n’y a pas de surcharge inutile. Dennis Zickefoose a proposé une configuration de délimiteur basée sur l’object dans un commentaire au code de Sven ci-dessous. Si vous le souhaitez, cela pourrait être implémenté comme une fonctionnalité alternative.
  • Il n’est actuellement pas évident de personnaliser les délimiteurs de conteneurs nesteds.
  • Gardez à l’esprit que l’objective de cette bibliothèque est de permettre des fonctions d’ impression rapide des conteneurs qui ne nécessitent aucun codage de votre part. Il ne s’agit pas d’une bibliothèque de formatage polyvalente, mais plutôt d’un outil de développement destiné à réduire le besoin d’écrire un code de plaque chauffante pour l’inspection des conteneurs.

Merci à tous ceux qui ont consortingbué!


Remarque: Si vous recherchez un moyen rapide de déployer des délimiteurs personnalisés, voici un moyen d’utiliser l’effacement de type. Nous supposons que vous avez déjà construit une classe de délimiteurs, disons MyDel , comme ceci:

 struct MyDel { static const pretty_print::delimiters_values values; }; const pretty_print::delimiters_values MyDel::values = { "" }; 

Maintenant, nous voulons être en mesure d’écrire std::cout << MyPrinter(v) << std::endl; pour certains conteneurs v utilisant ces délimiteurs. MyPrinter sera une classe d’effacement, comme ceci:

 struct wrapper_base { virtual ~wrapper_base() { } virtual std::ostream & stream(std::ostream & o) = 0; }; template  struct wrapper : public wrapper_base { wrapper(const T & t) : t(t) { } std::ostream & stream(std::ostream & o) { return o << pretty_print::print_container_helper<T, char, std::char_traits, Delims>(t); } private: const T & t; }; template  struct MyPrinter { template  MyPrinter(const Container & c) : base(new wrapper(c)) { } ~MyPrinter() { delete base; } wrapper_base * base; }; template  std::ostream & operator<<(std::ostream & o, const MyPrinter & p) { return p.base->stream(o); } 

Cette solution a été inspirée par la solution de Marcelo, avec quelques modifications:

 #include  #include  #include  #include  #include  // This works similar to ostream_iterator, but doesn't print a delimiter after the final item template > class pretty_ostream_iterator : public std::iterator { public: typedef TChar char_type; typedef TCharTraits traits_type; typedef std::basic_ostream ostream_type; pretty_ostream_iterator(ostream_type &stream, const char_type *delim = NULL) : _stream(&stream), _delim(delim), _insertDelim(false) { } pretty_ostream_iterator& operator=(const T &value) { if( _delim != NULL ) { // Don't insert a delimiter if this is the first time the function is called if( _insertDelim ) (*_stream) < < _delim; else _insertDelim = true; } (*_stream) << value; return *this; } pretty_ostream_iterator& operator*() { return *this; } pretty_ostream_iterator& operator++() { return *this; } pretty_ostream_iterator& operator++(int) { return *this; } private: ostream_type *_stream; const char_type *_delim; bool _insertDelim; }; #if _MSC_VER >= 1400 // Declare pretty_ostream_iterator as checked template struct std::_Is_checked_helper > : public std::tr1::true_type { }; #endif // _MSC_VER >= 1400 namespace std { // Pre-declarations of container types so we don't actually have to include the relevant headers if not needed, speeding up compilation time. // These aren't necessary if you do actually include the headers. template class vector; template class list; template class set; template class map; } // Basic is_container template; specialize to derive from std::true_type for all desired container types template struct is_container : public std::false_type { }; // Mark vector as a container template struct is_container > : public std::true_type { }; // Mark list as a container template struct is_container > : public std::true_type { }; // Mark set as a container template struct is_container > : public std::true_type { }; // Mark map as a container template struct is_container > : public std::true_type { }; // Holds the delimiter values for a specific character type template struct delimiters_values { typedef TChar char_type; const TChar *prefix; const TChar *delimiter; const TChar *postfix; }; // Defines the delimiter values for a specific container and character type template struct delimiters { static const delimiters_values values; }; // Default delimiters template struct delimiters { static const delimiters_values values; }; template const delimiters_values delimiters::values = { "{ ", ", ", " }" }; template struct delimiters { static const delimiters_values values; }; template const delimiters_values delimiters::values = { L"{ ", L", ", L" }" }; // Delimiters for set 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" ]" }; // Delimiters for pair 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")" }; // Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type. template, typename TDelimiters = delimiters > struct print_container_helper { typedef TChar char_type; typedef TDelimiters delimiters_type; typedef std::basic_ostream& ostream_type; print_container_helper(const T &container) : _container(&container) { } void operator()(ostream_type &stream) const { if( delimiters_type::values.prefix != NULL ) stream < < delimiters_type::values.prefix; std::copy(_container->begin(), _container->end(), pretty_ostream_iterator(stream, delimiters_type::values.delimiter)); if( delimiters_type::values.postfix != NULL ) stream < < delimiters_type::values.postfix; } private: const T *_container; }; // Prints a print_container_helper to the specified stream. template std::basic_ostream& operator< <(std::basic_ostream &stream, const print_container_helper &helper) { helper(stream); return stream; } // Prints a container to the stream using default delimiters template typename std::enable_if::value, std::basic_ostream&>::type operator< <(std::basic_ostream &stream, const T &container) { stream < < print_container_helper(container); return stream; } // Prints a pair to the stream using delimiters from delimiters>. template std::basic_ostream& operator< <(std::basic_ostream &stream, const std::pair &value) { if( delimiters, TChar>::values.prefix != NULL ) stream < < delimiters, TChar>::values.prefix; stream < < value.first; if( delimiters, TChar>::values.delimiter != NULL ) stream < < delimiters, TChar>::values.delimiter; stream < < value.second; if( delimiters, TChar>::values.postfix != NULL ) stream < < delimiters, TChar>::values.postfix; return stream; } // Used by the sample below to generate some values struct fibonacci { fibonacci() : f1(0), f2(1) { } int operator()() { int r = f1 + f2; f1 = f2; f2 = r; return f1; } private: int f1; int f2; }; int main() { std::vector v; std::generate_n(std::back_inserter(v), 10, fibonacci()); std::cout < < v << std::endl; // Example of using pretty_ostream_iterator directly std::generate_n(pretty_ostream_iterator(std::cout, ";"), 20, fibonacci()); std::cout < < std::endl; } 

Comme la version de Marcelo, elle utilise un trait de type is_container qui doit être spécialisé pour tous les conteneurs à prendre en charge. Il est peut-être possible d'utiliser un trait pour vérifier le type value_type , const_iterator , begin() / end() , mais je ne suis pas sûr de le recommander, car cela pourrait correspondre à des critères qui correspondent à ces critères mais ne sont pas des conteneurs. comme std::basic_ssortingng . À l'instar de la version de Marcelo, il utilise des modèles pouvant être spécialisés pour spécifier les délimiteurs à utiliser.

La principale différence est que j'ai construit ma version autour d'un pretty_ostream_iterator , qui fonctionne de manière similaire à std::ostream_iterator mais std::ostream_iterator pas de délimiteur après le dernier élément. Le formatage des conteneurs est effectué par le print_container_helper , qui peut être utilisé directement pour imprimer des conteneurs sans caractère is_container, ou pour spécifier un type de délimiteur différent.

J'ai également défini is_container et les délimiteurs, donc cela fonctionnera pour les conteneurs avec des prédicats ou des allocateurs non standard, et à la fois pour char et wchar_t. La fonction operator < < elle-même est également définie pour fonctionner à la fois avec les flux char et wchar_t.

Enfin, j'ai utilisé std::enable_if , qui est disponible dans le cadre de C ++ 0x, et fonctionne dans Visual C ++ 2010 et g ++ 4.3 (nécessite l'indicateur -std = c ++ 0x) et versions ultérieures. De cette façon, il n’ya pas de dépendance à Boost.

Cela a été modifié plusieurs fois, et nous avons décidé d’appeler la classe principale qui encapsule une collection RangePrinter

Cela devrait fonctionner automatiquement avec n’importe quelle collection une fois que vous avez écrit l’opérateur “overload”, sauf que vous aurez besoin d’un fichier spécial pour les cartes pour imprimer la paire et que vous souhaitiez y personnaliser le délimiteur.

Vous pourriez également avoir une fonction spéciale “print” à utiliser sur l’élément au lieu de simplement le sortir directement. Un peu comme les algorithmes STL vous permettent de passer des prédicats personnalisés. Avec map, vous l’utiliseriez de cette façon, avec une imprimante personnalisée pour la paire std ::.

Votre imprimante par défaut la sortirait simplement dans le stream.

OK, travaillons sur une imprimante personnalisée. Je vais changer ma classe externe en RangePrinter. Nous avons donc 2 iterators et certains délimiteurs, mais nous n’avons pas personnalisé l’impression des éléments réels.

 struct DefaultPrinter { template< typename T > std::ostream & operator()( std::ostream& os, const T& t ) const { return os < < t; } // overload for std::pair template< typename K, typename V > std::ostream & operator()( std::ostream & os, std::pair const& p) { return os < < p.first << '=' << p.second; } }; // some prototypes template< typename FwdIter, typename Printer > class RangePrinter; template< typename FwdIter, typename Printer > std::ostream & operator< <( std::ostream &, RangePrinter const& ); template< typename FwdIter, typename Printer=DefaultPrinter > class RangePrinter { FwdIter begin; FwdIter end; std::ssortingng delim; std::ssortingng open; std::ssortingng close; Printer printer; friend std::ostream& operator< < <>( std::ostream&, RangePrinter const& ); public: RangePrinter( FwdIter b, FwdIter e, Printer p, std::ssortingng const& d, std::ssortingng const & o, std::ssortingng const& c ) : begin( b ), end( e ), printer( p ), open( o ), close( c ) { } // with no "printer" variable RangePrinter( FwdIter b, FwdIter e, std::ssortingng const& d, std::ssortingng const & o, std::ssortingng const& c ) : begin( b ), end( e ), open( o ), close( c ) { } }; template std::ostream& operator< <( std::ostream& os, RangePrinter const& range ) { const Printer & printer = range.printer; os < < range.open; FwdIter begin = range.begin, end = range.end; // print the first item if (begin == end) { return os << range.close; } printer( os, *begin ); // print the rest with delim as a prefix for( ++begin; begin != end; ++begin ) { os << range.delim; printer( os, *begin ); } return os << range.close; } 

Now by default it will work for maps as long as the key and value types are both printable and you can put in your own special item printer for when they are not (as you can with any other type), or if you do not want = as the delimiter.

I am moving the free-function to create these to the end now:

A free-function (iterator version) would look like something this and you could even have defaults:

 template RangePrinter rangePrinter ( const Collection& coll, const char * delim=",", const char * open="[", const char * close="]") { return RangePrinter< typename Collection::const_iterator > ( coll.begin(), coll.end(), delim, open, close ); } 

You could then use it for std::set by

  std::cout < < outputFormatter( mySet ); 

You can also write free-function version that take a custom printer and ones that take two iterators. In any case they will resolve the template parameters for you, and you will be able to pass them through as temporaries.

Here is a working library, presented as a complete working program, that I just hacked together:

 #include  #include  #include  #include  // Default delimiters template  struct Delims { static const char *delim[3]; }; template  const char *Delims::delim[3]={"[", ", ", "]"}; // Special delimiters for sets. template  struct Delims< std::set > { static const char *delim[3]; }; template  const char *Delims< std::set >::delim[3]={"{", ", ", "}"}; template  struct IsContainer { enum { value = false }; }; template  struct IsContainer< std::vector > { enum { value = true }; }; template  struct IsContainer< std::set > { enum { value = true }; }; template  typename boost::enable_if, std::ostream&>::type operator< <(std::ostream & o, const C & x) { o << Delims::delim[0]; for (typename C::const_iterator i = x.begin(); i != x.end(); ++i) { if (i != x.begin()) o < < Delims::delim[1]; o < < *i; } o << Delims::delim[2]; return o; } template  struct IsChar { enum { value = false }; }; template <> struct IsChar { enum { value = true }; }; template  typename boost::disable_if, std::ostream&>::type operator< <(std::ostream & o, const T (&x)[N]) { o << "["; for (int i = 0; i != N; ++i) { if (i) o << ","; o << x[i]; } o << "]"; return o; } int main() { std::vector i; i.push_back(23); i.push_back(34); std::set j; j.insert("hello"); j.insert("world"); double k[] = { 1.1, 2.2, M_PI, -1.0/123.0 }; std::cout < < i << "\n" << j << "\n" << k << "\n"; } 

It currently only works with vector and set , but can be made to work with most containers, just by expanding on the IsContainer specializations. I haven't thought much about whether this code is minimal, but I can't immediately think of anything I could ssortingp out as redundant.

EDIT: Just for kicks, I included a version that handles arrays. I had to exclude char arrays to avoid further ambiguities; it might still get into trouble with wchar_t[] .

The code proved to be handy on several occasions now and I feel the expense to get into customization as usage is quite low. Thus, I decided to release it under MIT license and provide a GitHub repository where the header and a small example file can be downloaded.

http://djmuw.github.io/prettycc

0. Preface and wording

A ‘decoration’ in terms of this answer is a set of prefix-ssortingng, delimiter-ssortingng, and a postfix-ssortingng. Where the prefix ssortingng is inserted into a stream before and the postfix ssortingng after the values of a container (see 2. Target containers). The delimiter ssortingng is inserted between the values of the respective container.

Note: Actually, this answer does not address the question to 100% since the decoration is not ssortingctly comstackd time constant because runtime checks are required to check whether a custom decoration has been applied to the current stream. Nevertheless, I think it has some decent features.

Note2: May have minor bugs since it is not yet well tested.

1. General idea/usage

Zero additional code required for usage

It is to be kept as easy as

 #include  #include "pretty.h" int main() { std::cout < < std::vector{1,2,3,4,5}; // prints 1, 2, 3, 4, 5 return 0; } 

Easy customization …

… with respect to a specific stream object

 #include  #include "pretty.h" int main() { // set decoration for std::vector for cout object std::cout < < pretty::decoration>("(", ",", ")"); std::cout < < std::vector{1,2,3,4,5}; // prints (1,2,3,4,5) return 0; } 

or with respect to all streams:

 #include  #include "pretty.h" // set decoration for std::vector for all ostream objects PRETTY_DEFAULT_DECORATION(std::vector, "{", ", ", "}") int main() { std::cout < < std::vector{1,2,3,4,5}; // prints {1, 2, 3, 4, 5} std::cout < < pretty::decoration>("(", ",", ")"); std::cout < < std::vector{1,2,3,4,5}; // prints (1,2,3,4,5) return 0; } 

Rough description

  • The code includes a class template providing a default decoration for any type
  • which can be specialized to change the default decoration for (a) certain type(s) and it is
  • using the private storage provided by ios_base using xalloc / pword in order to save a pointer to a pretty::decor object specifically decorating a certain type on a certain stream.

If no pretty::decor object for this stream has been set up explicitly pretty::defaulted::decoration() is called to obtain the default decoration for the given type. The class pretty::defaulted is to be specialized to customize default decorations.

2. Target objects / containers

Target objects obj for the ‘pretty decoration’ of this code are objects having either

  • overloads std::begin and std::end defined (includes C-Style arrays),
  • having begin(obj) and end(obj) available via ADL,
  • are of type std::tuple
  • or of type std::pair .

The code includes a trait for identification of classes with range features ( begin / end ). (There’s no check included, whether begin(obj) == end(obj) is a valid expression, though.)

The code provides operator< < s in the global namespace that only apply to classes not having a more specialized version of operator< < available. Therefore, for example std::ssortingng is not printed using the operator in this code although having a valid begin / end pair.

3. Utilization and customization

Decorations can be imposed separately for every type (except different tuple s) and stream (not stream type!). (Ie a std::vector can have different decorations for different stream objects.)

A) Default decoration

The default prefix is "" (nothing) as is the default postfix, while the default delimiter is ", " (comma+space).

B) Customized default decoration of a type by specializing the pretty::defaulted class template

The struct defaulted has a static member function decoration() returning a decor object which includes the default values for the given type.

Example using an array:

Customize default array printing:

 namespace pretty { template struct defaulted { static decor decoration() { return{ { "(" }, { ":" }, { ")" } }; } }; } 

Print an arry array:

 float e[5] = { 3.4f, 4.3f, 5.2f, 1.1f, 22.2f }; std::cout < < e << '\n'; // prints (3.4:4.3:5.2:1.1:22.2) 

Using the PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) macro for char streams

The macro expands to

 namespace pretty { template< __VA_ARGS__ > struct defaulted< TYPE > { static decor< TYPE > decoration() { return { PREFIX, DELIM, POSTFIX }; } }; } 

enabling the above partial specialization to be rewritten to

 PRETTY_DEFAULT_DECORATION(T[N], "", ";", "", class T, std::size_t N) 

or inserting a full specialization like

 PRETTY_DEFAULT_DECORATION(std::vector, "(", ", ", ")") 

Another macro for wchar_t streams is included: PRETTY_DEFAULT_WDECORATION .

C) Impose decoration on streams

The function pretty::decoration is used to impose a decoration on a certain stream. There are overloads taking either - one ssortingng argument being the delimiter (adopting prefix and postfix from the defaulted class) - or three ssortingng arguments assembling the complete decoration

Complete decoration for given type and stream

 float e[3] = { 3.4f, 4.3f, 5.2f }; std::ssortingngstream u; // add { ; } decoration to u u < < pretty::decoration("{", "; ", "}"); // use { ; } decoration u < < e << '\n'; // prints {3.4; 4.3; 5.2} // uses decoration returned by defaulted::decoration() std::cout < < e; // prints 3.4, 4.3, 5.2 

Customization of delimiter for given stream

 PRETTY_DEFAULT_DECORATION(float[3], "{{{", ",", "}}}") std::ssortingngstream v; v < < e; // prints {{{3.4,4.3,5.2}}} v << pretty::decoration(":"); v < < e; // prints {{{3.4:4.3:5.2}}} v << pretty::decoration("((", "=", "))"); v < < e; // prints ((3.4=4.3=5.2)) 

4. Special handling of std::tuple

Instead of allowing a specialization for every possible tuple type, this code applies any decoration available for std::tuple to all kind of std::tuple< ...> s.

5. Remove custom decoration from the stream

To go back to the defaulted decoration for a given type use pretty::clear function template on the stream s .

 s < < pretty::clear>(); 

5. Further examples

Printing "masortingx-like" with newline delimiter

 std::vector> m{ {1,2,3}, {4,5,6}, {7,8,9} }; std::cout < < pretty::decoration>>("\n"); std::cout < < m; 

Des tirages

 1, 2, 3 4, 5, 6 7, 8, 9 

See it on ideone/KKUebZ

6. Code

 #ifndef pretty_print_0x57547_sa4884X_0_1_h_guard_ #define pretty_print_0x57547_sa4884X_0_1_h_guard_ #include  #include  #include  #include  #include  #define PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) \ namespace pretty { template< __VA_ARGS__ >\ struct defaulted< TYPE > {\ static decor< TYPE > decoration(){\ return { PREFIX, DELIM, POSTFIX };\ } /*decoration*/ }; /*defaulted*/} /*pretty*/ #define PRETTY_DEFAULT_WDECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) \ namespace pretty { template< __VA_ARGS__ >\ struct defaulted< TYPE, wchar_t, std::char_traits > {\ static decor< TYPE, wchar_t, std::char_traits > decoration(){\ return { PREFIX, DELIM, POSTFIX };\ } /*decoration*/ }; /*defaulted*/} /*pretty*/ namespace pretty { namespace detail { // drag in begin and end overloads using std::begin; using std::end; // helper template template  using _ol = std::integral_constant*; // SFINAE check whether T is a range with begin/end template class is_range { // helper function declarations using expression sfinae template  = nullptr> static std::false_type b(...); template  = nullptr> static auto b(U &v) -> decltype(begin(v), std::true_type()); template  = nullptr> static std::false_type e(...); template  = nullptr> static auto e(U &v) -> decltype(end(v), std::true_type()); // return types using b_return = decltype(b(std::declval())); using e_return = decltype(e(std::declval())); public: static const bool value = b_return::value && e_return::value; }; } // holder class for data template> struct decor { static const int xindex; std::basic_ssortingng prefix, delimiter, postfix; decor(std::basic_ssortingng const & pre = "", std::basic_ssortingng const & delim = "", std::basic_ssortingng const & post = "") : prefix(pre), delimiter(delim), postfix(post) {} }; template int const decor::xindex = std::ios_base::xalloc(); namespace detail { template void manage_decor(std::ios_base::event evt, std::ios_base &s, int const idx) { using deco_type = decor; if (evt == std::ios_base::erase_event) { // erase deco void const * const p = s.pword(idx); if (p) { delete static_cast(p); s.pword(idx) = nullptr; } } else if (evt == std::ios_base::copyfmt_event) { // copy deco void const * const p = s.pword(idx); if (p) { auto np = new deco_type{ *static_cast(p) }; s.pword(idx) = static_cast(np); } } } template struct clearer {}; template std::basic_ostream& operator< < ( std::basic_ostream &s, clearer const &) { using deco_type = decor; void const * const p = s.pword(deco_type::xindex); if (p) { // delete if set delete static_cast(p); s.pword(deco_type::xindex) = nullptr; } return s; } template  struct default_data { static const CharT * decor[3]; }; template <> const char * default_data::decor[3] = { "", ", ", "" }; template <> const wchar_t * default_data::decor[3] = { L"", L", ", L"" }; } // Clear decoration for T template detail::clearer clear() { return{}; } template void clear(std::basic_ostream &s) { s < < detail::clearer{}; } // impose decoration on ostream template std::basic_ostream& operator< <( std::basic_ostream &s, decor && h) { using deco_type = decor; void const * const p = s.pword(deco_type::xindex); // delete if already set if (p) delete static_cast(p); s.pword(deco_type::xindex) = static_cast(new deco_type{ std::move(h) }); // check whether we alread have a callback registered if (s.iword(deco_type::xindex) == 0) { // if this is not the case register callback and set iword s.register_callback(detail::manage_decor, deco_type::xindex); s.iword(deco_type::xindex) = 1; } return s; } template> struct defaulted { static inline decor decoration() { return{ detail::default_data::decor[0], detail::default_data::decor[1], detail::default_data::decor[2] }; } }; template> decor decoration( std::basic_ssortingng const & prefix, std::basic_ssortingng const & delimiter, std::basic_ssortingng const & postfix) { return{ prefix, delimiter, postfix }; } template> decor decoration( std::basic_ssortingng const & delimiter) { using str_type = std::basic_ssortingng; return{ defaulted::decoration().prefix, delimiter, defaulted::decoration().postfix }; } template> decor decoration(CharT const * const prefix, CharT const * const delimiter, CharT const * const postfix) { using str_type = std::basic_ssortingng; return{ str_type{ prefix }, str_type{ delimiter }, str_type{ postfix } }; } template> decor decoration(CharT const * const delimiter) { using str_type = std::basic_ssortingng; return{ defaulted::decoration().prefix, str_type{ delimiter }, defaulted::decoration().postfix }; } template struct tuple { template static void print(std::basic_ostream& s, T const & value, std::basic_ssortingng const &delimiter) { s < < std::get(value) < < delimiter; tuple::print(s, value, delimiter); } }; template struct tuple { template static void print(std::basic_ostream& s, T const & value, std::basic_ssortingng const &) { s < < std::get(value); } }; } template std::basic_ostream & operator< < ( std::basic_ostream &s, std::tuple<> const & v) { using deco_type = pretty::decor, CharT, TraitT>; using defaulted_type = pretty::defaulted, CharT, TraitT>; void const * const p = s.pword(deco_type::xindex); auto const d = static_cast(p); s < < (d ? d->prefix : defaulted_type::decoration().prefix); s < < (d ? d->postfix : defaulted_type::decoration().postfix); return s; } template std::basic_ostream & operator< < ( std::basic_ostream &s, std::tuple const & v) { using deco_type = pretty::decor, CharT, TraitT>; using defaulted_type = pretty::defaulted, CharT, TraitT>; using pretty_tuple = pretty::tuple, 0U, sizeof...(T)-1U>; void const * const p = s.pword(deco_type::xindex); auto const d = static_cast(p); s < < (d ? d->prefix : defaulted_type::decoration().prefix); pretty_tuple::print(s, v, d ? d->delimiter : defaulted_type::decoration().delimiter); s < < (d ? d->postfix : defaulted_type::decoration().postfix); return s; } template std::basic_ostream & operator< < ( std::basic_ostream &s, std::pair const & v) { using deco_type = pretty::decor, CharT, TraitT>; using defaulted_type = pretty::defaulted, CharT, TraitT>; void const * const p = s.pword(deco_type::xindex); auto const d = static_cast(p); s < < (d ? d->prefix : defaulted_type::decoration().prefix); s < < v.first; s << (d ? d->delimiter : defaulted_type::decoration().delimiter); s < < v.second; s << (d ? d->postfix : defaulted_type::decoration().postfix); return s; } template> typename std::enable_if < pretty::detail::is_range::value, std::basic_ostream < CharT, TraitT >> ::type & operator< < ( std::basic_ostream &s, T const & v) { bool first(true); using deco_type = pretty::decor; using default_type = pretty::defaulted; void const * const p = s.pword(deco_type::xindex); auto d = static_cast const * const>(p); s < < (d ? d->prefix : default_type::decoration().prefix); for (auto const & e : v) { // v is range thus range based for works if (!first) s < < (d ? d->delimiter : default_type::decoration().delimiter); s < < e; first = false; } s << (d ? d->postfix : default_type::decoration().postfix); return s; } #endif // pretty_print_0x57547_sa4884X_0_1_h_guard_ 

I am going to add another answer here, because I have come up with a different approach to my previous one, and that is to use locale facets.

The basics are here

Essentially what you do is:

  1. Create a class that derives from std::locale::facet . The slight downside is that you will need a compilation unit somewhere to hold its id. Let’s call it MyPrettyVectorPrinter. You’d probably give it a better name, and also create ones for pair and map.
  2. In your stream function, you check std::has_facet< MyPrettyVectorPrinter >
  3. If that returns true, extract it with std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. Your facet objects will have values for the delimiters and you can read them. If the facet isn’t found, your print function ( operator< < ) provides default ones. Note you can do the same thing for reading a vector.

I like this method because you can use a default print whilst still being able to use a custom override.

The downsides are needing a library for your facet if used in multiple projects (so can't just be headers-only) and also the fact that you need to beware about the expense of creating a new locale object.

I have written this as a new solution rather than modify my other one because I believe both approaches can be correct and you take your pick.

My solution is simple.h , which is part of scc package. All std containers, maps, sets, c-arrays are printable.

The goal here is to use ADL to do customization of how we pretty print.

You pass in a formatter tag, and override 4 functions (before, after, between and descend) in the tag’s namespace. This changes how the formatter prints ‘adornments’ when iterating over containers.

A default formatter that does {(a->b),(c->d)} for maps, (a,b,c) for tupleoids, "hello" for ssortingngs, [x,y,z] for everything else included.

It should “just work” with 3rd party iterable types (and treat them like “everything else”).

If you want custom adornments for your 3rd party iterables, simply create your own tag. It will take a bit of work to handle map descent (you need to overload pretty_print_descend( your_tag to return pretty_print::decorator::map_magic_tag ). Maybe there is a cleaner way to do this, not sure.

A little library to detect iterability, and tuple-ness:

 namespace details { using std::begin; using std::end; template struct is_iterable_test:std::false_type{}; template struct is_iterable_test())==end(std::declval())) , ((void)(std::next(begin(std::declval())))) , ((void)(*begin(std::declval()))) , 1 )) >:std::true_type{}; templatestruct is_tupleoid:std::false_type{}; templatestruct is_tupleoid>:std::true_type{}; templatestruct is_tupleoid>:std::true_type{}; // templatestruct is_tupleoid>:std::true_type{}; // complete, but problematic } templatestruct is_iterable:details::is_iterable_test>{}; templatestruct is_iterable:std::true_type{}; // bypass decay templatestruct is_tupleoid:details::is_tupleoid>{}; templatestruct is_visitable:std::integral_constant{}||is_tupleoid{}> {}; 

A library that lets us visit the contents of an iterable or tuple type object:

 template std::enable_if_t{}> visit_first(C&& c, F&& f) { using std::begin; using std::end; auto&& b = begin(c); auto&& e = end(c); if (b==e) return; std::forward(f)(*b); } template std::enable_if_t{}> visit_all_but_first(C&& c, F&& f) { using std::begin; using std::end; auto it = begin(c); auto&& e = end(c); if (it==e) return; it = std::next(it); for( ; it!=e; it = std::next(it) ) { f(*it); } } namespace details { template void visit_first( std::index_sequence<>, Tup&&, F&& ) {} template void visit_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) { std::forward(f)( std::get<0>( std::forward(tup) ) ); } template void visit_all_but_first( std::index_sequence<>, Tup&&, F&& ) {} template void visit_all_but_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) { int unused[] = {0,((void)( f( std::get(std::forward(tup)) ) ),0)...}; (void)(unused); } } template std::enable_if_t{}> visit_first(Tup&& tup, F&& f) { details::visit_first( std::make_index_sequence< std::tuple_size>{} >{}, std::forward(tup), std::forward(f) ); } template std::enable_if_t{}> visit_all_but_first(Tup&& tup, F&& f) { details::visit_all_but_first( std::make_index_sequence< std::tuple_size>{} >{}, std::forward(tup), std::forward(f) ); } 

A pretty printing library:

 namespace pretty_print { namespace decorator { struct default_tag {}; template struct map_magic_tag:Old {}; // magic for maps // Maps get {}s. Write trait `is_associative` to generalize: template void pretty_print_before( default_tag, std::basic_ostream& s, std::map const& ) { s < < CharT('{'); } template void pretty_print_after( default_tag, std::basic_ostream& s, std::map const& ) { s < < CharT('}'); } // tuples and pairs get (): template std::enable_if_t{}> pretty_print_before( default_tag, std::basic_ostream& s, Tup const& ) { s < < CharT('('); } template std::enable_if_t{}> pretty_print_after( default_tag, std::basic_ostream& s, Tup const& ) { s < < CharT(')'); } // strings with the same character type get ""s: template void pretty_print_before( default_tag, std::basic_ostream& s, std::basic_ssortingng const& ) { s < < CharT('"'); } template void pretty_print_after( default_tag, std::basic_ostream& s, std::basic_ssortingng const& ) { s < < CharT('"'); } // and pack the characters together: template void pretty_print_between( default_tag, std::basic_ostream&, std::basic_ssortingng const& ) {} // map magic. When iterating over the contents of a map, use the map_magic_tag: template map_magic_tag pretty_print_descend( default_tag, std::map const& ) { return {}; } template old_tag pretty_print_descend( map_magic_tag, C const& ) { return {}; } // When printing a pair immediately within a map, use -> as a separator: template void pretty_print_between( map_magic_tag, std::basic_ostream& s, std::pair const& ) { s < < CharT('-') << CharT('>'); } } // default behavior: template void pretty_print_before( Tag const&, std::basic_ostream& s, Container const& ) { s < < CharT('['); } template void pretty_print_after( Tag const&, std::basic_ostream& s, Container const& ) { s < < CharT(']'); } template void pretty_print_between( Tag const&, std::basic_ostream& s, Container const& ) { s < < CharT(','); } template Tag&& pretty_print_descend( Tag&& tag, Container const& ) { return std::forward(tag); } // print things by default by using < <: template std::enable_if_t< !is_visitable{}> print( std::basic_ostream& os, Scalar&& scalar, Tag&&=Tag{} ) { os < < std::forward(scalar); } // for anything visitable (see above), use the pretty print algorithm: template std::enable_if_t{}> print( std::basic_ostream& os, C&& c, Tag&& tag=Tag{} ) { pretty_print_before( std::forward(tag), os, std::forward(c) ); visit_first( c, [&](auto&& elem) { print( os, std::forward(elem), pretty_print_descend( std::forward(tag), std::forward(c) ) ); }); visit_all_but_first( c, [&](auto&& elem) { pretty_print_between( std::forward(tag), os, std::forward(c) ); print( os, std::forward(elem), pretty_print_descend( std::forward(tag), std::forward(c) ) ); }); pretty_print_after( std::forward(tag), os, std::forward(c) ); } } 

Code de test:

 int main() { std::vector x = {1,2,3}; pretty_print::print( std::cout, x ); std::cout < < "\n"; std::map< std::string, int > m; m["hello"] = 3; m["world"] = 42; pretty_print::print( std::cout, m ); std::cout < < "\n"; } 

live example

This does use C++14 features (some _t aliases, and auto&& lambdas), but none are essential.