Contenitori STL di C ++ in bella stampa

Si prega di prendere nota degli aggiornamenti alla fine di questo post.

Aggiornamento: ho creato un progetto pubblico su GitHub per questa libreria!


Mi piacerebbe avere un singolo modello che, una volta per tutte, si occupi della stampa di tutti i contenitori STL tramite l’ operator<< . In pseudo codice, sto cercando qualcosa di simile a questo:

 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; } 

Ora ho visto un sacco di modelli di magia qui su SO che non avrei mai pensato ansible, quindi mi chiedo se qualcuno può suggerire qualcosa che possa corrispondere a tutti i contenitori C. Forse qualcosa trait-ish che può capire se qualcosa ha l’iteratore necessario ?

Grazie molto!


Aggiornamento (e soluzione)

Dopo aver sollevato nuovamente questo problema su Canale 9 , ho ricevuto una risposta fantastica da Sven Groot, che, combinata con un po ‘di caratteri di tipo SFINAE, sembra risolvere il problema in modo completamente generale e incastrato. I delimitatori possono essere specializzati individualmente, un esempio di specializzazione per std :: set è incluso, nonché un esempio di utilizzo di delimitatori personalizzati.

L’helper “wrap_array ()” può essere utilizzato per stampare array C grezzi. Aggiornamento: sono disponibili per la stampa coppie e tuple; i delimitatori di default sono parentesi tonde.

Il tipo di carattere enable-if richiede C ++ 0x, ma con alcune modifiche dovrebbe essere ansible creare una versione C ++ 98 di questo. Le tuple richiedono modelli variadici, quindi C ++ 0x.

Ho chiesto a Sven di pubblicare la soluzione qui in modo che io possa accettarla, ma nel frattempo vorrei pubblicare il codice personalmente per riferimento. ( Aggiornamento: Sven ha ora pubblicato il suo codice qui sotto, che ho fatto la risposta accettata.Il mio codice utilizza tratti di tipo contenitore, che funzionano per me ma possono causare un comportamento imprevisto con classi non contenitore che forniscono iteratori.)

Intestazione (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 compiler 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 

Esempio di utilizzo:

 #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::string 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 ; } 

Ulteriori idee per miglioramenti:

  • Implementa l’output per std::tuple nello stesso modo in cui lo abbiamo per std::pair . Aggiornamento: questa è ora una domanda separata su SO ! Upupdate: ora è stato implementato, grazie a Xeo!
  • Aggiungere spazi dei nomi in modo che le classi helper non sanguinino nello spazio dei nomi globale. Fatto
  • Aggiungi alias di modello (o qualcosa di simile) per facilitare la creazione di classi di delimitatori personalizzate o forse macro del preprocessore?

Aggiornamenti recenti:

  • Ho rimosso l’iteratore di output personalizzato a favore di un ciclo semplice per la funzione di stampa.
  • Tutti i dettagli di implementazione sono ora nello spazio pretty_print nomi pretty_print . Solo gli operatori di stream globale e il wrapper pretty_print_array trovano nello spazio dei nomi globale.
  • Corretto il namespacing in modo che l’ operator<< ora sia correttamente in std .

Gli appunti:

  • Rimuovere l’iteratore di output significa che non c’è modo di usare std::copy() per ottenere una bella stampa. Potrei reintegrare il bel iteratore se questa è una caratteristica desiderata, ma il codice di Sven qui sotto ha l’implementazione.
  • È stata una decisione consapevole del progetto rendere i delimitatori delle costanti di compilazione piuttosto che delle costanti dell’object. Ciò significa che non è ansible fornire i delimitatori dynamicmente in fase di runtime, ma significa anche che non c’è un sovraccarico non necessario. Una configurazione delimitatore basata su oggetti è stata proposta da Dennis Zickefoose in un commento al seguente codice di Sven. Se lo si desidera, questo potrebbe essere implementato come una funzione alternativa.
  • Al momento non è ovvio come personalizzare i delimitatori del contenitore nidificati.
  • Tieni presente che lo scopo di questa libreria è di consentire servizi di stampa di contenitori rapidi che richiedono la codifica zero della tua parte. Non è una libreria di formattazione per tutti gli usi, ma piuttosto uno strumento di sviluppo per alleviare la necessità di scrivere il codice della piastra della caldaia per l’ispezione del contenitore.

Grazie a tutti coloro che hanno contribuito!


Nota: se stai cercando un modo rapido per distribuire i delimitatori personalizzati, ecco un modo per utilizzare la cancellazione dei tipi. Supponiamo che tu abbia già costruito una class di delimitazione, ad esempio MyDel , in questo modo:

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

Ora vogliamo essere in grado di scrivere std::cout << MyPrinter(v) << std::endl; per qualche contenitore v usa quei delimitatori. MyPrinter sarà una class di cancellazione del tipo, in questo modo:

 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); } 

Questa soluzione è stata ispirata dalla soluzione di Marcelo, con alcune modifiche:

 #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; } 

Come la versione di Marcelo, usa un tratto di tipo is_container che deve essere specializzato per tutti i contenitori che devono essere supportati. Potrebbe essere ansible utilizzare un tratto per verificare value_type , const_iterator , begin() / end() , ma non sono sicuro che lo consiglierei poiché potrebbe corrispondere a elementi che corrispondono a tali criteri ma che in realtà non sono contenitori, come std::basic_string . Analogamente alla versione di Marcelo, utilizza modelli che possono essere specializzati per specificare i delimitatori da usare.

La differenza principale è che ho costruito la mia versione attorno a pretty_ostream_iterator , che funziona in modo simile a std::ostream_iterator ma non stampa un delimitatore dopo l'ultimo elemento. La formattazione dei contenitori viene eseguita da print_container_helper , che può essere utilizzata direttamente per stampare contenitori senza un carattere is_container o per specificare un tipo di delimitatore diverso.

Ho anche definito is_container e delimitatori, quindi funzionerà per contenitori con predicati o allocatori non standard e sia per char che per wchar_t. Anche la funzione operatore < < è definita per funzionare con entrambi i flussi char e wchar_t.

Infine, ho usato std::enable_if , che è disponibile come parte di C ++ 0x, e funziona in Visual C ++ 2010 e g ++ 4.3 (richiede il flag -std = c ++ 0x) e successivamente. In questo modo non vi è alcuna dipendenza da Boost.

Questo è stato modificato alcune volte e abbiamo deciso di chiamare la class principale che include una raccolta RangePrinter

Questo dovrebbe funzionare automaticamente con qualsiasi raccolta una volta che hai scritto l’operatore di una volta < < sovraccarico, eccetto che avrai bisogno di uno speciale per le mappe per stampare la coppia e potresti voler personalizzare il delimitatore lì.

Potresti anche avere una speciale funzione “stampa” da usare sull’object invece di emetterlo direttamente. Un po ‘come gli algoritmi STL ti permettono di passare in predicati personalizzati. Con la mappa lo useresti in questo modo, con una stampante personalizzata per la coppia std :: pair.

La tua stampante “predefinita” lo manderebbe al stream.

Ok, lavoriamo su una stampante personalizzata. Cambierò la mia class esterna in RangePrinter. Quindi abbiamo 2 iteratori e alcuni delimitatori ma non abbiamo personalizzato come stampare gli oggetti reali.

 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::string delim; std::string open; std::string close; Printer printer; friend std::ostream& operator< < <>( std::ostream&, RangePrinter const& ); public: RangePrinter( FwdIter b, FwdIter e, Printer p, std::string const& d, std::string const & o, std::string const& c ) : begin( b ), end( e ), printer( p ), open( o ), close( c ) { } // with no "printer" variable RangePrinter( FwdIter b, FwdIter e, std::string const& d, std::string const & o, std::string 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 strip 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-string, delimiter-string, and a postfix-string. Where the prefix string is inserted into a stream before and the postfix string after the values of a container (see 2. Target containers). The delimiter string 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 strictly compiled 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 classs 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 classs not having a more specialized version of operator< < available. Therefore, for example std::string 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 string argument being the delimiter (adopting prefix and postfix from the defaulted class) - or three string arguments assembling the complete decoration

Complete decoration for given type and stream

 float e[3] = { 3.4f, 4.3f, 5.2f }; std::stringstream 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::stringstream 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 "matrix-like" with newline delimiter

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

stampe

 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_string prefix, delimiter, postfix; decor(std::basic_string const & pre = "", std::basic_string const & delim = "", std::basic_string 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_string const & prefix, std::basic_string const & delimiter, std::basic_string const & postfix) { return{ prefix, delimiter, postfix }; } template> decor decoration( std::basic_string const & delimiter) { using str_type = std::basic_string; 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_string; return{ str_type{ prefix }, str_type{ delimiter }, str_type{ postfix } }; } template> decor decoration(CharT const * const delimiter) { using str_type = std::basic_string; 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_string 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_string 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 strings, [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_string const& ) { s < < CharT('"'); } template void pretty_print_after( default_tag, std::basic_ostream& s, std::basic_string const& ) { s < < CharT('"'); } // and pack the characters together: template void pretty_print_between( default_tag, std::basic_ostream&, std::basic_string 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) ); } } 

Codice di prova:

 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.