vectors librerias libreria iterador estandar ejemplos ejemplo dev c++ templates c++11 operator-overloading pretty-print

librerias - Contenedores C++ STL de impresión bonita



vector stl c++ ejemplos (7)

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 la impresión de todos los contenedores STL a través del operator<< . En pseudo código, estoy buscando algo como esto:

template<container C, class T, String delim = ", ", String open = "[", String close = "]"> std::ostream & operator<<(std::ostream & o, const C<T> & 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 un montón de magia de plantillas aquí, de modo que nunca pensé que fuera posible, así que me pregunto si alguien puede sugerir algo que coincida con todos los contenedores C. Tal vez algo que pueda descubrir si algo tiene el iterador necesario ?

¡Muchas gracias!

Actualización (y solución)

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

El ayudante "wrap_array ()" se puede utilizar para imprimir matrices C en bruto. Actualización: los pares y las tuplas están disponibles para imprimir; Los delimitadores predeterminados son paréntesis redondos.

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

Le he pedido a Sven que publique la solución aquí para poder aceptarla, pero mientras tanto me gustaría publicar el código para referencia. ( Actualización: Sven ahora ha publicado su código a continuación, que hice la respuesta aceptada. Mi propio código utiliza rasgos de tipo contenedor, que funcionan para mí pero pueden causar un comportamiento inesperado con clases que no son contenedores que proporcionan iteradores).

Encabezado (prettyprint.h):

#ifndef H_PRETTY_PRINT #define H_PRETTY_PRINT #include <type_traits> #include <iostream> #include <utility> #include <tuple> 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<typename T, typename TTraits, typename TAllocator> 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<typename T> struct is_container_helper { private: template<typename C> static char test(typename C::const_iterator*); template<typename C> static int test(...); public: static const bool value = sizeof(test<T>(0)) == sizeof(char); }; // Basic is_container template; specialize to derive from std::true_type for all desired container types template<typename T> struct is_container : public ::std::integral_constant<bool, is_container_helper<T>::value> { }; // Holds the delimiter values for a specific character type template<typename TChar> 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<typename T, typename TChar> struct delimiters { typedef delimiters_values<TChar> type; static const type values; }; // Default delimiters template<typename T> struct delimiters<T, char> { static const delimiters_values<char> values; }; template<typename T> const delimiters_values<char> delimiters<T, char>::values = { "[", ", ", "]" }; template<typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; }; template<typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"[", L", ", L"]" }; // Delimiters for set template<typename T, typename TTraits, typename TAllocator> struct delimiters< ::std::set<T, TTraits, TAllocator>, char> { static const delimiters_values<char> values; }; template<typename T, typename TTraits, typename TAllocator> const delimiters_values<char> delimiters< ::std::set<T, TTraits, TAllocator>, char>::values = { "{", ", ", "}" }; template<typename T, typename TTraits, typename TAllocator> struct delimiters< ::std::set<T, TTraits, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; }; template<typename T, typename TTraits, typename TAllocator> const delimiters_values<wchar_t> delimiters< ::std::set<T, TTraits, TAllocator>, wchar_t>::values = { L"{", L", ", L"}" }; // Delimiters for pair (reused for tuple, see below) template<typename T1, typename T2> struct delimiters< ::std::pair<T1, T2>, char> { static const delimiters_values<char> values; }; template<typename T1, typename T2> const delimiters_values<char> delimiters< ::std::pair<T1, T2>, char>::values = { "(", ", ", ")" }; template<typename T1, typename T2> struct delimiters< ::std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; }; template<typename T1, typename T2> const delimiters_values<wchar_t> delimiters< ::std::pair<T1, T2>, 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<TChar>, typename TDelimiters = delimiters<T, TChar>> struct print_container_helper { typedef TChar char_type; typedef TDelimiters delimiters_type; typedef std::basic_ostream<TChar, TCharTraits> & 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<TChar> and TChar = char or wchar_t, and MyDelims needs to be defined for TChar. // Usage: "cout << pretty_print::custom_delims<MyDelims>(x)". struct custom_delims_base { virtual ~custom_delims_base() { } virtual ::std::ostream & stream(::std::ostream &) = 0; virtual ::std::wostream & stream(::std::wostream &) = 0; }; template <typename T, typename Delims> 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<char>, Delims>(t); } ::std::wostream & stream(::std::wostream & stream) { return stream << ::pretty_print::print_container_helper<T, wchar_t, ::std::char_traits<wchar_t>, Delims>(t); } private: const T & t; }; template <typename Delims> struct custom_delims { template <typename Container> custom_delims(const Container & c) : base(new custom_delims_wrapper<Container, Delims>(c)) { } ~custom_delims() { delete base; } custom_delims_base * base; }; } // namespace pretty_print template <typename TChar, typename TCharTraits, typename Delims> inline std::basic_ostream<TChar, TCharTraits> & operator<<(std::basic_ostream<TChar, TCharTraits> & stream, const pretty_print::custom_delims<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<T, C, A> const sdelims::type sdelims<std::set<T,C,A>>::values = { ... }." //template<typename T> using pp_sdelims = pretty_print::delimiters<T, char>; //template<typename T> using pp_wsdelims = pretty_print::delimiters<T, wchar_t>; namespace std { // Prints a print_container_helper to the specified stream. template<typename T, typename TChar, typename TCharTraits, typename TDelimiters> inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const ::pretty_print::print_container_helper<T, TChar, TCharTraits, TDelimiters> & helper) { helper(stream); return stream; } // Prints a container to the stream using default delimiters template<typename T, typename TChar, typename TCharTraits> inline typename enable_if< ::pretty_print::is_container<T>::value, basic_ostream<TChar, TCharTraits>&>::type operator<<(basic_ostream<TChar, TCharTraits> & stream, const T & container) { return stream << ::pretty_print::print_container_helper<T, TChar, TCharTraits>(container); } // Prints a pair to the stream using delimiters from delimiters<std::pair<T1, T2>>. template<typename T1, typename T2, typename TChar, typename TCharTraits> inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const pair<T1, T2> & value) { if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.prefix != NULL) stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.prefix; stream << value.first; if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.delimiter != NULL) stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.delimiter; stream << value.second; if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.postfix != NULL) stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.postfix; return stream; } } // namespace std // Prints a tuple to the stream using delimiters from delimiters<std::pair<tuple_dummy_t, tuple_dummy_t>>. namespace pretty_print { struct tuple_dummy_t { }; // Just if you want special delimiters for tuples. typedef std::pair<tuple_dummy_t, tuple_dummy_t> tuple_dummy_pair; template<typename Tuple, size_t N, typename TChar, typename TCharTraits> struct pretty_tuple_helper { static inline void print(::std::basic_ostream<TChar, TCharTraits> & stream, const Tuple & value) { pretty_tuple_helper<Tuple, N - 1, TChar, TCharTraits>::print(stream, value); if (delimiters<tuple_dummy_pair, TChar>::values.delimiter != NULL) stream << delimiters<tuple_dummy_pair, TChar>::values.delimiter; stream << std::get<N - 1>(value); } }; template<typename Tuple, typename TChar, typename TCharTraits> struct pretty_tuple_helper<Tuple, 1, TChar, TCharTraits> { static inline void print(::std::basic_ostream<TChar, TCharTraits> & stream, const Tuple & value) { stream << ::std::get<0>(value); } }; } // namespace pretty_print namespace std { template<typename TChar, typename TCharTraits, typename ...Args> inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const tuple<Args...> & value) { if (::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.prefix != NULL) stream << ::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.prefix; ::pretty_print::pretty_tuple_helper<const tuple<Args...> &, sizeof...(Args), TChar, TCharTraits>::print(stream, value); if (::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.postfix != NULL) stream << ::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::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 <typename T, size_t N> 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 <typename T, size_t N> inline pretty_print::array_wrapper<T, N> pretty_print_array(const T (& a)[N]) { return pretty_print::array_wrapper<T, N>(a); } #endif

Ejemplo de uso:

#include <iostream> #include <vector> #include <unordered_map> #include <map> #include <set> #include <array> #include <tuple> #include <utility> #include <string> #include "prettyprint.h" // Specialization for a particular container template<> const pretty_print::delimiters_values<char> pretty_print::delimiters<std::vector<double>, char>::values = { "|| ", " : ", " ||" }; // Custom delimiters for one-off use struct MyDel { static const delimiters_values<char> values; }; const delimiters_values<char> MyDel::values = { "<", "; ", ">" }; int main(int argc, char * argv[]) { std::string cs; std::unordered_map<int, std::string> um; std::map<int, std::string> om; std::set<std::string> ss; std::vector<std::string> v; std::vector<std::vector<std::string>> vv; std::vector<std::pair<int, std::string>> vp; std::vector<double> 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<int, std::string> 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<char, 5> 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<std::string>, char, std::char_traits<char>, MyDel>(v) << std::endl; // Using custom delimiters with the type-erasing helper class std::cout << pretty_print::custom_delims<MyDel>(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<double, std::string>(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 ; }

Más ideas para mejoras:

  • Implemente la salida para std::tuple<...> de la misma manera que lo tenemos para std::pair<S,T> . Actualización: ¡ Esta es ahora una pregunta separada sobre SO ! Upupdate: ¡ Esto ya se ha implementado, gracias a Xeo!
  • Agregue espacios de nombres para que las clases auxiliares no sangren en el espacio de nombres global. Hecho
  • ¿Agregar alias de plantilla (o algo similar) para facilitar la creación de clases delimitadoras personalizadas, o quizás macros de preprocesador?

Actualizaciones recientes:

  • Eliminé el iterador de salida personalizado a favor de un simple bucle for en la función de impresión.
  • Todos los detalles de la implementación están ahora en el espacio de nombres de pretty_print . Solo los operadores de flujo global y la envoltura pretty_print_array están en el espacio de nombres global.
  • Se corrigió el espacio de nombre 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 restablecer el bonito iterador si esta es una característica deseada, pero el código de Sven a continuación tiene la implementación.
  • Fue una decisión de diseño consciente hacer que los delimitadores sean constantes en tiempo de compilación en lugar de constantes de objeto. Eso significa que no puede suministrar delimitadores dinámicamente en tiempo de ejecución, pero también significa que no hay gastos generales 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 contenedores anidados.
  • Tenga en cuenta que el propósito de esta biblioteca es permitir instalaciones de impresión de contenedores rápidos que requieran una codificación cero de su parte. No es una biblioteca de formateo de uso múltiple, sino una herramienta de desarrollo para aliviar la necesidad de escribir el código de la placa de la 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 tipo. Suponemos que ya ha construido una clase de delimitador, digamos MyDel , así:

struct MyDel { static const pretty_print::delimiters_values<char> values; }; const pretty_print::delimiters_values<char> MyDel::values = { "<", "; ", ">" };

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

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


Esta solución se inspiró en la solución de Marcelo, con algunos cambios:

#include <iostream> #include <iterator> #include <type_traits> #include <vector> #include <algorithm> // This works similar to ostream_iterator, but doesn''t print a delimiter after the final item template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar> > class pretty_ostream_iterator : public std::iterator<std::output_iterator_tag, void, void, void, void> { public: typedef TChar char_type; typedef TCharTraits traits_type; typedef std::basic_ostream<TChar, TCharTraits> ostream_type; pretty_ostream_iterator(ostream_type &stream, const char_type *delim = NULL) : _stream(&stream), _delim(delim), _insertDelim(false) { } pretty_ostream_iterator<T, TChar, TCharTraits>& 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<T, TChar, TCharTraits>& operator*() { return *this; } pretty_ostream_iterator<T, TChar, TCharTraits>& operator++() { return *this; } pretty_ostream_iterator<T, TChar, TCharTraits>& 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<typename T, typename TChar, typename TCharTraits> struct std::_Is_checked_helper<pretty_ostream_iterator<T, TChar, TCharTraits> > : 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<typename T, typename TAllocator> class vector; template<typename T, typename TAllocator> class list; template<typename T, typename TTraits, typename TAllocator> class set; template<typename TKey, typename TValue, typename TTraits, typename TAllocator> class map; } // Basic is_container template; specialize to derive from std::true_type for all desired container types template<typename T> struct is_container : public std::false_type { }; // Mark vector as a container template<typename T, typename TAllocator> struct is_container<std::vector<T, TAllocator> > : public std::true_type { }; // Mark list as a container template<typename T, typename TAllocator> struct is_container<std::list<T, TAllocator> > : public std::true_type { }; // Mark set as a container template<typename T, typename TTraits, typename TAllocator> struct is_container<std::set<T, TTraits, TAllocator> > : public std::true_type { }; // Mark map as a container template<typename TKey, typename TValue, typename TTraits, typename TAllocator> struct is_container<std::map<TKey, TValue, TTraits, TAllocator> > : public std::true_type { }; // Holds the delimiter values for a specific character type template<typename TChar> 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<typename T, typename TChar> struct delimiters { static const delimiters_values<TChar> values; }; // Default delimiters template<typename T> struct delimiters<T, char> { static const delimiters_values<char> values; }; template<typename T> const delimiters_values<char> delimiters<T, char>::values = { "{ ", ", ", " }" }; template<typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; }; template<typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"{ ", L", ", L" }" }; // Delimiters for set template<typename T, typename TTraits, typename TAllocator> struct delimiters<std::set<T, TTraits, TAllocator>, char> { static const delimiters_values<char> values; }; template<typename T, typename TTraits, typename TAllocator> const delimiters_values<char> delimiters<std::set<T, TTraits, TAllocator>, char>::values = { "[ ", ", ", " ]" }; template<typename T, typename TTraits, typename TAllocator> struct delimiters<std::set<T, TTraits, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; }; template<typename T, typename TTraits, typename TAllocator> const delimiters_values<wchar_t> delimiters<std::set<T, TTraits, TAllocator>, wchar_t>::values = { L"[ ", L", ", L" ]" }; // Delimiters for pair template<typename T1, typename T2> struct delimiters<std::pair<T1, T2>, char> { static const delimiters_values<char> values; }; template<typename T1, typename T2> const delimiters_values<char> delimiters<std::pair<T1, T2>, char>::values = { "(", ", ", ")" }; template<typename T1, typename T2> struct delimiters<std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; }; template<typename T1, typename T2> const delimiters_values<wchar_t> delimiters<std::pair<T1, T2>, 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<TChar>, typename TDelimiters = delimiters<T, TChar> > struct print_container_helper { typedef TChar char_type; typedef TDelimiters delimiters_type; typedef std::basic_ostream<TChar, TCharTraits>& 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<typename T::value_type, TChar, TCharTraits>(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<typename T, typename TChar, typename TCharTraits, typename TDelimiters> std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const print_container_helper<T, TChar, TDelimiters> &helper) { helper(stream); return stream; } // Prints a container to the stream using default delimiters template<typename T, typename TChar, typename TCharTraits> typename std::enable_if<is_container<T>::value, std::basic_ostream<TChar, TCharTraits>&>::type operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const T &container) { stream << print_container_helper<T, TChar, TCharTraits>(container); return stream; } // Prints a pair to the stream using delimiters from delimiters<std::pair<T1, T2>>. template<typename T1, typename T2, typename TChar, typename TCharTraits> std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const std::pair<T1, T2> &value) { if( delimiters<std::pair<T1, T2>, TChar>::values.prefix != NULL ) stream << delimiters<std::pair<T1, T2>, TChar>::values.prefix; stream << value.first; if( delimiters<std::pair<T1, T2>, TChar>::values.delimiter != NULL ) stream << delimiters<std::pair<T1, T2>, TChar>::values.delimiter; stream << value.second; if( delimiters<std::pair<T1, T2>, TChar>::values.postfix != NULL ) stream << delimiters<std::pair<T1, T2>, 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<int> 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<int>(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 ser 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 recomiendo ya que puede coincidir con cosas que coinciden con esos criterios pero no son en realidad contenedores, como std::basic_string . También al igual que la versión de Marcelo, usa plantillas que pueden especializarse para especificar los delimitadores a usar.

La principal diferencia es que he construido mi versión en torno a un pretty_ostream_iterator , que funciona de forma similar al std::ostream_iterator pero no imprime un delimitador después del último elemento. El formateo de los contenedores lo realiza el print_container_helper , que se puede usar directamente para imprimir contenedores sin un rasgo de is_container, o para especificar un tipo de delimitador diferente.

También he definido is_container y delimiters para que funcione con contenedores con predicados o asignadores no estándar, y para char y wchar_t. La función del operador << también se define para trabajar 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 la marca -std = c ++ 0x) y versiones posteriores. De esta manera no hay dependencia en Boost.


Esto se ha editado varias 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 por única vez << excepto que necesitará una especial para que los mapas impriman el par, y es posible que desee personalizar el delimitador allí.

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

Su impresora "predeterminada" simplemente la enviará a la transmisión.

Ok, vamos a trabajar en una impresora personalizada. Cambiaré mi clase externa a RangePrinter. 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<K,V> 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<FwdIter, Printer> 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<FwdIter,Printer> 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<typename FwdIter, typename Printer> std::ostream& operator<<( std::ostream& os, RangePrinter<FwdIter, Printer> 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; }

Ahora, de forma predeterminada, funcionará para mapas siempre y cuando los tipos de clave y valor sean imprimibles y usted pueda colocar su propia impresora de elementos especiales para cuando no lo estén (como puede hacerlo con cualquier otro tipo), o si no desea = como el delimitador.

Estoy moviendo la función libre para crear estos hasta el final ahora:

Una función libre (versión de iterador) se vería como algo así e incluso podría tener valores predeterminados:

template<typename Collection> RangePrinter<typename Collection::const_iterator> 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 ); }

Entonces podrías usarlo para std :: set by

std::cout << outputFormatter( mySet );

También puede escribir una versión de función libre que tome una impresora personalizada y una que tome dos iteradores. En cualquier caso, resolverán los parámetros de la plantilla por usted, y usted podrá pasarlos como temporales.


El código demostró ser útil en varias ocasiones ahora y siento que el costo de personalización es muy bajo. Por lo tanto, decidí lanzarlo bajo la licencia MIT y proporcionar un repositorio GitHub donde se puedan descargar el encabezado y un pequeño archivo de ejemplo.

http://djmuw.github.io/prettycc

0. Prefacio y redacción.

Una "decoración" en términos de esta respuesta es un conjunto de cadena de prefijo, cadena de delimitador y una cadena de sufijo. Donde la cadena de prefijo se inserta en una secuencia antes y la cadena de postfix después de los valores de un contenedor (ver 2. Contenedores de destino). La cadena delimitadora se inserta entre los valores del contenedor respectivo.

Nota: en realidad, esta respuesta no aborda la pregunta al 100% ya que la decoración no es una constante de tiempo compilada estrictamente porque se requieren verificaciones de tiempo de ejecución para verificar si se ha aplicado una decoración personalizada a la secuencia actual. Sin embargo, creo que tiene algunas características decentes.

Nota 2: puede tener errores menores ya que aún no está bien probado.

1. Idea general / uso

Cero código adicional requerido para el uso

Se debe mantener tan fácil como

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

Fácil personalización ...

... con respecto a un objeto de flujo específico

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

o con respecto a todas las corrientes:

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

Descripción aproximada

  • El código incluye una plantilla de clase que proporciona una decoración predeterminada para cualquier tipo
  • que puede ser especializado para cambiar la decoración por defecto para (a) cierto tipo (s) y es
  • usando el almacenamiento privado provisto ios_baseusando xalloc/ pwordpara guardar un puntero a un pretty::decorobjeto específicamente decorando cierto tipo en un flujo determinado

Si no pretty::decor<T>se ha configurado explícitamente ningún objeto para este flujo, pretty::defaulted<T, charT, chartraitT>::decoration()se llama para obtener la decoración predeterminada para el tipo dado. La clase pretty::defaulteddebe ser especializada para personalizar decoraciones por defecto.

2. Objetos objetivo / contenedores

Los objetos de destino objpara la "decoración bonita" de este código son objetos que tienen

  • sobrecargas std::beginy std::enddefinidas (incluye matrices estilo C),
  • Tener begin(obj)y end(obj)disponible a través de ADL,
  • son de tipo std::tuple
  • o de tipo std::pair.

El código incluye un rasgo para la identificación de clases con características de rango ( begin/ end). (Sin begin(obj) == end(obj)embargo, no se incluye ninguna verificación, ya sea ​​una expresión válida).

El código proporciona operator<<s en el espacio de nombres global que solo se aplica a las clases que no tienen una versión más especializada de operator<<disponible. Por lo tanto, por ejemplo, std::stringno se imprime utilizando el operador en este código aunque tenga un par begin/ válido end.

3. Utilización y personalización.

Las decoraciones se pueden imponer por separado para cada tipo (excepto las diferentes tuples) y para la transmisión (¡no para el tipo de transmisión!). (Es decir, una std::vector<int>lata puede tener diferentes decoraciones para diferentes objetos de corriente).

A) Decoración por defecto

El prefijo predeterminado es ""(nada) como es el postfix predeterminado, mientras que el delimitador predeterminado es ", "(coma + espacio).

B) Decoración predeterminada personalizada de un tipo especializando la pretty::defaultedplantilla de clase

El struct defaultedtiene una función miembro estática que decoration()devuelve un decorobjeto que incluye los valores predeterminados para el tipo dado.

Ejemplo usando una matriz:

Personalice la impresión de matriz predeterminada:

namespace pretty { template<class T, std::size_t N> struct defaulted<T[N]> { static decor<T[N]> decoration() { return{ { "(" }, { ":" }, { ")" } }; } }; }

Imprimir una matriz arry:

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)

Usando la PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)macro para charstreams

La macro se expande a

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

permitiendo reescribir la especialización parcial anterior a

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

o insertando una especialización completa como

PRETTY_DEFAULT_DECORATION(std::vector<int>, "(", ", ", ")")

Otra macro para wchar_tcorrientes se incluye: PRETTY_DEFAULT_WDECORATION.

C) Imponer decoración en arroyos.

La función pretty::decorationse utiliza para imponer una decoración en un determinado flujo. Hay sobrecargas que toman cualquiera de ellas: un argumento de cadena es el delimitador (que adopta el prefijo y el postfijo de la clase predeterminada) o tres argumentos de cadena que reúnen la decoración completa

Decoración completa para tipo y flujo dado.

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

Personalización del delimitador para flujo dado.

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

4. Manejo especial de std::tuple

En lugar de permitir una especialización para cada tipo de tupla posible, este código aplica cualquier decoración disponible para std::tuple<void*>todo tipo de std::tuple<...>s.

5. Eliminar la decoración personalizada de la corriente

Para volver a la decoración predeterminada para un tipo determinado, use la pretty::clearplantilla de función en el flujo s.

s << pretty::clear<std::vector<int>>();

5. Otros ejemplos

Impresión "tipo matriz" con delimitador de nueva línea

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

Huellas dactilares

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

ideone/KKUebZ en ideone/KKUebZ

6. Código

#ifndef pretty_print_0x57547_sa4884X_0_1_h_guard_ #define pretty_print_0x57547_sa4884X_0_1_h_guard_ #include <string> #include <iostream> #include <type_traits> #include <iterator> #include <utility> #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<wchar_t> > {/ static decor< TYPE, wchar_t, std::char_traits<wchar_t> > 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 <int I> using _ol = std::integral_constant<int, I>*; // SFINAE check whether T is a range with begin/end template<class T> class is_range { // helper function declarations using expression sfinae template <class U, _ol<0> = nullptr> static std::false_type b(...); template <class U, _ol<1> = nullptr> static auto b(U &v) -> decltype(begin(v), std::true_type()); template <class U, _ol<0> = nullptr> static std::false_type e(...); template <class U, _ol<1> = nullptr> static auto e(U &v) -> decltype(end(v), std::true_type()); // return types using b_return = decltype(b<T>(std::declval<T&>())); using e_return = decltype(e<T>(std::declval<T&>())); public: static const bool value = b_return::value && e_return::value; }; } // holder class for data template<class T, class CharT = char, class TraitT = std::char_traits<CharT>> struct decor { static const int xindex; std::basic_string<CharT, TraitT> prefix, delimiter, postfix; decor(std::basic_string<CharT, TraitT> const & pre = "", std::basic_string<CharT, TraitT> const & delim = "", std::basic_string<CharT, TraitT> const & post = "") : prefix(pre), delimiter(delim), postfix(post) {} }; template<class T, class charT, class traits> int const decor<T, charT, traits>::xindex = std::ios_base::xalloc(); namespace detail { template<class T, class CharT, class TraitT> void manage_decor(std::ios_base::event evt, std::ios_base &s, int const idx) { using deco_type = decor<T, CharT, TraitT>; if (evt == std::ios_base::erase_event) { // erase deco void const * const p = s.pword(idx); if (p) { delete static_cast<deco_type const * const>(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<deco_type const * const>(p) }; s.pword(idx) = static_cast<void*>(np); } } } template<class T> struct clearer {}; template<class T, class CharT, class TraitT> std::basic_ostream<CharT, TraitT>& operator<< ( std::basic_ostream<CharT, TraitT> &s, clearer<T> const &) { using deco_type = decor<T, CharT, TraitT>; void const * const p = s.pword(deco_type::xindex); if (p) { // delete if set delete static_cast<deco_type const *>(p); s.pword(deco_type::xindex) = nullptr; } return s; } template <class CharT> struct default_data { static const CharT * decor[3]; }; template <> const char * default_data<char>::decor[3] = { "", ", ", "" }; template <> const wchar_t * default_data<wchar_t>::decor[3] = { L"", L", ", L"" }; } // Clear decoration for T template<class T> detail::clearer<T> clear() { return{}; } template<class T, class CharT, class TraitT> void clear(std::basic_ostream<CharT, TraitT> &s) { s << detail::clearer<T>{}; } // impose decoration on ostream template<class T, class CharT, class TraitT> std::basic_ostream<CharT, TraitT>& operator<<( std::basic_ostream<CharT, TraitT> &s, decor<T, CharT, TraitT> && h) { using deco_type = decor<T, CharT, TraitT>; void const * const p = s.pword(deco_type::xindex); // delete if already set if (p) delete static_cast<deco_type const *>(p); s.pword(deco_type::xindex) = static_cast<void *>(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<T, CharT, TraitT>, deco_type::xindex); s.iword(deco_type::xindex) = 1; } return s; } template<class T, class CharT = char, class TraitT = std::char_traits<CharT>> struct defaulted { static inline decor<T, CharT, TraitT> decoration() { return{ detail::default_data<CharT>::decor[0], detail::default_data<CharT>::decor[1], detail::default_data<CharT>::decor[2] }; } }; template<class T, class CharT = char, class TraitT = std::char_traits<CharT>> decor<T, CharT, TraitT> decoration( std::basic_string<CharT, TraitT> const & prefix, std::basic_string<CharT, TraitT> const & delimiter, std::basic_string<CharT, TraitT> const & postfix) { return{ prefix, delimiter, postfix }; } template<class T, class CharT = char, class TraitT = std::char_traits < CharT >> decor<T, CharT, TraitT> decoration( std::basic_string<CharT, TraitT> const & delimiter) { using str_type = std::basic_string<CharT, TraitT>; return{ defaulted<T, CharT, TraitT>::decoration().prefix, delimiter, defaulted<T, CharT, TraitT>::decoration().postfix }; } template<class T, class CharT = char, class TraitT = std::char_traits < CharT >> decor<T, CharT, TraitT> decoration(CharT const * const prefix, CharT const * const delimiter, CharT const * const postfix) { using str_type = std::basic_string<CharT, TraitT>; return{ str_type{ prefix }, str_type{ delimiter }, str_type{ postfix } }; } template<class T, class CharT = char, class TraitT = std::char_traits < CharT >> decor<T, CharT, TraitT> decoration(CharT const * const delimiter) { using str_type = std::basic_string<CharT, TraitT>; return{ defaulted<T, CharT, TraitT>::decoration().prefix, str_type{ delimiter }, defaulted<T, CharT, TraitT>::decoration().postfix }; } template<typename T, std::size_t N, std::size_t L> struct tuple { template<class CharT, class TraitT> static void print(std::basic_ostream<CharT, TraitT>& s, T const & value, std::basic_string<CharT, TraitT> const &delimiter) { s << std::get<N>(value) << delimiter; tuple<T, N + 1, L>::print(s, value, delimiter); } }; template<typename T, std::size_t N> struct tuple<T, N, N> { template<class CharT, class TraitT> static void print(std::basic_ostream<CharT, TraitT>& s, T const & value, std::basic_string<CharT, TraitT> const &) { s << std::get<N>(value); } }; } template<class CharT, class TraitT> std::basic_ostream<CharT, TraitT> & operator<< ( std::basic_ostream<CharT, TraitT> &s, std::tuple<> const & v) { using deco_type = pretty::decor<std::tuple<void*>, CharT, TraitT>; using defaulted_type = pretty::defaulted<std::tuple<void*>, CharT, TraitT>; void const * const p = s.pword(deco_type::xindex); auto const d = static_cast<deco_type const * const>(p); s << (d ? d->prefix : defaulted_type::decoration().prefix); s << (d ? d->postfix : defaulted_type::decoration().postfix); return s; } template<class CharT, class TraitT, class ... T> std::basic_ostream<CharT, TraitT> & operator<< ( std::basic_ostream<CharT, TraitT> &s, std::tuple<T...> const & v) { using deco_type = pretty::decor<std::tuple<void*>, CharT, TraitT>; using defaulted_type = pretty::defaulted<std::tuple<void*>, CharT, TraitT>; using pretty_tuple = pretty::tuple<std::tuple<T...>, 0U, sizeof...(T)-1U>; void const * const p = s.pword(deco_type::xindex); auto const d = static_cast<deco_type const * const>(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<class T, class U, class CharT, class TraitT> std::basic_ostream<CharT, TraitT> & operator<< ( std::basic_ostream<CharT, TraitT> &s, std::pair<T, U> const & v) { using deco_type = pretty::decor<std::pair<T, U>, CharT, TraitT>; using defaulted_type = pretty::defaulted<std::pair<T, U>, CharT, TraitT>; void const * const p = s.pword(deco_type::xindex); auto const d = static_cast<deco_type const * const>(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<class T, class CharT = char, class TraitT = std::char_traits < CharT >> typename std::enable_if < pretty::detail::is_range<T>::value, std::basic_ostream < CharT, TraitT >> ::type & operator<< ( std::basic_ostream<CharT, TraitT> &s, T const & v) { bool first(true); using deco_type = pretty::decor<T, CharT, TraitT>; using default_type = pretty::defaulted<T, CharT, TraitT>; void const * const p = s.pword(deco_type::xindex); auto d = static_cast<pretty::decor<T, CharT, TraitT> 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_


Aquí hay una biblioteca de trabajo, presentada como un programa de trabajo completo, que acabo de hackear juntos:

#include <set> #include <vector> #include <iostream> #include <boost/utility/enable_if.hpp> // Default delimiters template <class C> struct Delims { static const char *delim[3]; }; template <class C> const char *Delims<C>::delim[3]={"[", ", ", "]"}; // Special delimiters for sets. template <typename T> struct Delims< std::set<T> > { static const char *delim[3]; }; template <typename T> const char *Delims< std::set<T> >::delim[3]={"{", ", ", "}"}; template <class C> struct IsContainer { enum { value = false }; }; template <typename T> struct IsContainer< std::vector<T> > { enum { value = true }; }; template <typename T> struct IsContainer< std::set<T> > { enum { value = true }; }; template <class C> typename boost::enable_if<IsContainer<C>, std::ostream&>::type operator<<(std::ostream & o, const C & x) { o << Delims<C>::delim[0]; for (typename C::const_iterator i = x.begin(); i != x.end(); ++i) { if (i != x.begin()) o << Delims<C>::delim[1]; o << *i; } o << Delims<C>::delim[2]; return o; } template <typename T> struct IsChar { enum { value = false }; }; template <> struct IsChar<char> { enum { value = true }; }; template <typename T, int N> typename boost::disable_if<IsChar<T>, 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<int> i; i.push_back(23); i.push_back(34); std::set<std::string> 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"; }

Actualmente solo funciona con vectory set, pero se puede hacer que funcione con la mayoría de los contenedores, simplemente ampliando las IsContainerespecializaciones. No he pensado mucho sobre si este código es mínimo, pero no puedo pensar de inmediato en algo que pudiera eliminar como redundante.

EDITAR: Sólo por diversión, incluí una versión que maneja arreglos. Tuve que excluir matrices de caracteres para evitar más ambigüedades; Todavía podría tener problemas con wchar_t[].


El objetivo aquí es usar ADL para hacer la personalización de cómo imprimimos.

Usted pasa una etiqueta del formateador y anula 4 funciones (antes, después, entre y desciende) en el espacio de nombres de la etiqueta. Esto cambia la forma en que el formateador imprime "adornos" al iterar sobre contenedores.

Un formateador predeterminado que hace {(a->b),(c->d)}para mapas, (a,b,c)para tupleoids, "hello"para cadenas, [x,y,z]para todo lo demás incluido.

Debe "simplemente trabajar" con tipos iterables de terceros (y tratarlos como "todo lo demás").

Si desea adornos personalizados para sus iterables de terceros, simplemente cree su propia etiqueta. Tomará un poco de trabajo manejar el descenso del mapa (debe sobrecargarse pretty_print_descend( your_tagpara regresar pretty_print::decorator::map_magic_tag<your_tag>). Tal vez hay una forma más limpia de hacer esto, no estoy seguro.

Una pequeña biblioteca para detectar iterabilidad, y tuple-ness:

namespace details { using std::begin; using std::end; template<class T, class=void> struct is_iterable_test:std::false_type{}; template<class T> struct is_iterable_test<T, decltype((void)( (void)(begin(std::declval<T>())==end(std::declval<T>())) , ((void)(std::next(begin(std::declval<T>())))) , ((void)(*begin(std::declval<T>()))) , 1 )) >:std::true_type{}; template<class T>struct is_tupleoid:std::false_type{}; template<class...Ts>struct is_tupleoid<std::tuple<Ts...>>:std::true_type{}; template<class...Ts>struct is_tupleoid<std::pair<Ts...>>:std::true_type{}; // template<class T, size_t N>struct is_tupleoid<std::array<T,N>>:std::true_type{}; // complete, but problematic } template<class T>struct is_iterable:details::is_iterable_test<std::decay_t<T>>{}; template<class T, std::size_t N>struct is_iterable<T(&)[N]>:std::true_type{}; // bypass decay template<class T>struct is_tupleoid:details::is_tupleoid<std::decay_t<T>>{}; template<class T>struct is_visitable:std::integral_constant<bool, is_iterable<T>{}||is_tupleoid<T>{}> {};

Una biblioteca que nos permite visitar los contenidos de un objeto iterable o tipo tupla:

template<class C, class F> std::enable_if_t<is_iterable<C>{}> 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>(f)(*b); } template<class C, class F> std::enable_if_t<is_iterable<C>{}> 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<class Tup, class F> void visit_first( std::index_sequence<>, Tup&&, F&& ) {} template<size_t... Is, class Tup, class F> void visit_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) { std::forward<F>(f)( std::get<0>( std::forward<Tup>(tup) ) ); } template<class Tup, class F> void visit_all_but_first( std::index_sequence<>, Tup&&, F&& ) {} template<size_t... Is,class Tup, class F> void visit_all_but_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) { int unused[] = {0,((void)( f( std::get<Is>(std::forward<Tup>(tup)) ) ),0)...}; (void)(unused); } } template<class Tup, class F> std::enable_if_t<is_tupleoid<Tup>{}> visit_first(Tup&& tup, F&& f) { details::visit_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) ); } template<class Tup, class F> std::enable_if_t<is_tupleoid<Tup>{}> visit_all_but_first(Tup&& tup, F&& f) { details::visit_all_but_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) ); }

Una bonita biblioteca de impresión:

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

Código de prueba:

int main() { std::vector<int> 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"; }

ejemplo vivo

Esto utiliza características de C ++ 14 (algunos _talias y auto&&lambdas), pero ninguna es esencial.


Voy a agregar otra respuesta aquí, porque se me ocurrió un enfoque diferente al anterior, y es usar facetas de locale.

Lo básico está here

Esencialmente lo que haces es:

  1. Crear una clase que derive de std::locale::facet. El pequeño inconveniente es que necesitará una unidad de compilación en algún lugar para mantener su id. Llamémoslo MyPrettyVectorPrinter. Probablemente le darías un nombre mejor, y también crearías uno para el par y el mapa.
  2. En su función de transmisión, usted verifica std::has_facet< MyPrettyVectorPrinter >
  3. Si eso devuelve cierto, extraelo con std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. Sus objetos de faceta tendrán valores para los delimitadores y usted puede leerlos. Si no se encuentra la faceta, su función de impresión ( operator<<) proporciona las predeterminadas. Tenga en cuenta que puede hacer lo mismo para leer un vector.

Me gusta este método porque puede usar una impresión predeterminada mientras aún puede usar una anulación personalizada.

Las desventajas son la necesidad de una biblioteca para su faceta si se usa en múltiples proyectos (por lo que no puede ser solo de encabezados) y también el hecho de que debe tener cuidado con el costo de crear un nuevo objeto de configuración regional.

He escrito esto como una nueva solución en lugar de modificar mi otra porque creo que ambos enfoques pueden ser correctos y usted elige.


Mi solución es simple.h , que es parte del paquete scc . Todos los contenedores estándar, mapas, conjuntos, arrays c son imprimibles.