Bastante impresos contenedores C ++ STL

Por favor, tome nota de las actualizaciones al final de esta publicación.

Actualización: ¡He creado un proyecto público en GitHub para esta biblioteca!


Me gustaría tener una única plantilla que de una vez por todas se encargue de imprimir bastante bien todos los contenedores STL a través del operator<< . En pseudo código, estoy buscando algo como esto:

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

Ahora he visto mucha magia de plantilla aquí en SO que nunca pensé que fuera posible, así que me pregunto si alguien puede sugerir algo que coincida con todos los contenedores C. Quizás algo característico que pueda descubrir si algo tiene el iterador necesario ?

¡Muchas gracias!


Actualización (y solución)

Después de plantear este problema nuevamente en el canal 9 , obtuve una respuesta fantástica de Sven Groot, que, combinada con un poco de la característica de tipo SFINAE, parece resolver el problema de una manera completamente general y encajable. Los delimitadores se pueden especializar individualmente, se incluye una especialización de ejemplo para std :: set, así como un ejemplo de uso de delimitadores personalizados.

El helper “wrap_array ()” se puede usar para imprimir matrices C en bruto. Actualización: pares y tuplas están disponibles para imprimir; los delimitadores por defecto son corchetes.

El rasgo de tipo enable-if requiere C ++ 0x, pero con algunas modificaciones debería ser posible hacer una versión C ++ 98 de esto. Las tuplas requieren plantillas variadas, por lo tanto C ++ 0x.

Le pedí a Sven que publicara la solución aquí para que yo pueda aceptarla, pero mientras tanto me gustaría publicar el código yo mismo como referencia. ( Actualización: Sven ahora ha publicado su código a continuación, que hice la respuesta aceptada. Mi propio código utiliza características de tipo de contenedor, que funcionan para mí, pero pueden causar un comportamiento inesperado con clases que no son de contenedor y que proporcionan iteradores).

Encabezado (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 comstacktion 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 

Ejemplo de uso:

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

Otras ideas para mejoras:

  • Implemente la salida para std::tuple de la misma forma que lo tenemos para std::pair . Actualización: ¡ Ahora esta es una pregunta separada sobre SO ! Upupdate: ¡ Esto ya se implementó, gracias a Xeo!
  • Agregue espacios de nombres para que las clases auxiliares no sangren en el espacio de nombres global. Hecho
  • Agregue alias de plantilla (o algo similar) para facilitar la creación de clases de delimitadores personalizados, o tal vez macros de preprocesador.

Actualizaciones recientes:

  • Eliminé el iterador de salida personalizado a favor de un bucle for simple en la función de impresión.
  • Todos los detalles de implementación están ahora en el espacio de nombres pretty_print . Solo los operadores de flujo global y el contenedor pretty_print_array están en el espacio de nombres global.
  • Se corrigió el espacio de nombres para que el operator<< ahora esté correctamente en std .

Notas:

  • Eliminar el iterador de salida significa que no hay forma de usar std::copy() para obtener una impresión bonita. Podría reinstalar el iterador bonito si esta es una característica deseada, pero el siguiente código de Sven tiene la implementación.
  • Fue una decisión de diseño consciente hacer los delimitadores constantes en tiempo de comstackción en lugar de constantes de objeto. Eso significa que no puede suministrar delimitadores dinámicamente en el tiempo de ejecución, pero también significa que no hay gastos indirectos innecesarios. Dennis Zickefoose ha propuesto una configuración de delimitador basada en objetos en un comentario al código de Sven a continuación. Si lo desea, esto podría implementarse como una característica alternativa.
  • Actualmente no es obvio cómo personalizar los delimitadores de contenedor nesteds.
  • Tenga en cuenta que el objective de esta biblioteca es permitir instalaciones rápidas de impresión de contenedores que requieren una encoding cero de su parte. No es una biblioteca de formato para todo uso, sino más bien una herramienta de desarrollo para aliviar la necesidad de escribir un código de placa de caldera para la inspección de contenedores.

¡Gracias a todos los que contribuyeron!


Nota: Si está buscando una forma rápida de implementar delimitadores personalizados, aquí hay una forma de utilizar el borrado de tipos. Suponemos que ya ha construido una clase de delimitador, digamos MyDel , así:

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

Ahora queremos poder escribir std::cout << MyPrinter(v) << std::endl; para algún contenedor v usando esos delimitadores. MyPrinter será una clase de borrado de tipos, así:

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

Esta solución fue inspirada por la solución de Marcelo, con algunos cambios:

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

Al igual que la versión de Marcelo, utiliza un rasgo de tipo is_container que debe estar especializado para todos los contenedores que se admitirán. Puede ser posible usar un rasgo para verificar value_type , const_iterator , begin() / end() , pero no estoy seguro de que lo recomiende, ya que podría coincidir con las cosas que coinciden con esos criterios, pero en realidad no son contenedores, como std::basic_string . También, al igual que la versión de Marcelo, utiliza plantillas que pueden especializarse para especificar los delimitadores que se usarán.

La principal diferencia es que he creado mi versión alrededor de un pretty_ostream_iterator , que funciona de manera similar a std::ostream_iterator pero no imprime un delimitador después del último elemento. El formato de los contenedores se realiza mediante print_container_helper , que se puede usar directamente para imprimir contenedores sin un rasgo is_container, o para especificar un tipo de delimitadores diferente.

También he definido is_container y delimitadores, por lo que funcionará para contenedores con predicados o asignadores no estándar, y para char y wchar_t. La función del operador << también está definida para funcionar con las secuencias char y wchar_t.

Finalmente, he usado std::enable_if , que está disponible como parte de C ++ 0x, y funciona en Visual C ++ 2010 y g ++ 4.3 (necesita el indicador -std = c ++ 0x) y más tarde. De esta forma no hay dependencia en Boost.

Esto ha sido editado algunas veces, y hemos decidido llamar a la clase principal que envuelve una colección RangePrinter

Esto debería funcionar automáticamente con cualquier colección una vez que haya escrito la sobrecarga del operador de una sola vez, excepto que necesitará una especial para que los mapas impriman el par, y puede que desee personalizar el delimitador allí.

También podría tener una función especial de “impresión” para usar en el elemento en lugar de simplemente imprimirlo directamente. Un poco como los algoritmos STL le permiten pasar predicados personalizados. Con el mapa, lo usaría de esta manera, con una impresora personalizada para std :: pair.

Su impresora “predeterminada” simplemente la enviaría a la transmisión.

Ok, trabajemos en una impresora personalizada. Cambiaré mi clase externa a RangePrinter. Así que tenemos 2 iteradores y algunos delimitadores, pero no hemos personalizado cómo imprimir los elementos reales.

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

Huellas dactilares

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

Código de prueba:

 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.