Довольно печатные контейнеры STL STL

Пожалуйста, обратите внимание на обновления в конце этого сообщения.

Обновление: я создал публичный проект GitHub для этой библиотеки!


Я хотел бы иметь один шаблон, который раз и навсегда позаботится о том, чтобы печатать все контейнеры STL через operator<< . В псевдокоде я ищу что-то вроде этого:

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

Теперь я видел много шаблонов магии здесь на SO, что я никогда не думал, возможно, так что мне интересно, если кто-нибудь может предложить что-то, что будет соответствовать всем контейнерам C. Может быть, что-то trait-ish, который может понять, если что-то имеет необходимый iterator ?

Большое спасибо!


Обновление (и решение)

После поднятия этой проблемы снова на канале 9 , я получил фантастический ответ от Sven Groot, который, в сочетании с небольшим типом обучения SFINAE, как представляется, решает проблему в полностью общем и вложенном порядке. Разделители могут быть индивидуально специализированными, включена специализированная специализация для std :: set, а также пример использования пользовательских разделителей.

Помощник «wrap_array ()» может использоваться для печати исходных массивов C. Обновление: Пары и кортежи доступны для печати; разделители по умолчанию являются круглыми скобками.

Для типа enable-if требуется C ++ 0x, но с некоторыми изменениями он должен быть доступен для версии C ++ 98. Кортежи требуют вариационных шаблонов, поэтому C ++ 0x.

Я попросил Свена разместить здесь решение, чтобы я мог его принять, но тем временем я хотел бы сам написать код для справки. ( Обновление: теперь Свен разместил свой код ниже, и я принял принятый ответ. В моем собственном коде используются свойства типа контейнера, которые работают для меня, но могут вызвать неожиданное поведение с неконтейнерными classами, которые предоставляют iteratorы.)

Заголовок (prettyprint.h):

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

Пример использования:

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

Дальнейшие идеи для улучшения:

  • Реализовать вывод для std::tuple таким же образом, мы имеем его для std::pair . Обновление: теперь это отдельный вопрос о SO ! Upupdate: теперь это реализовано благодаря Xeo!
  • Добавьте пространства имен, чтобы classы-помощники не сливались в глобальное пространство имен. Готово
  • Добавьте псевдонимы шаблонов (или что-то подобное) для облегчения создания пользовательских classов разделителей или, возможно, макросов препроцессора?

Недавние обновления:

  • Я удалил пользовательский выходной iterator в пользу простого цикла в функции печати.
  • Все детали реализации теперь находятся в пространстве имен pretty_print . Только глобальные streamовые операторы и shell pretty_print_array находятся в глобальном пространстве имен.
  • Исправлено пространство имен, так что operator<< теперь корректно в std .

Заметки:

  • Удаление выходного iteratorа означает, что нет возможности использовать std::copy() для получения довольно-печатной информации. Я мог бы восстановить симпатичный iterator, если это желаемая функция, но код Свена ниже имеет реализацию.
  • Было сознательным проектным решением сделать константы компиляции времени разделителей, а не объектные константы. Это означает, что вы не можете динамически распределять разделители во время выполнения, но это также означает, что нет ненужных накладных расходов. Конфигурация разделителя на основе объектов была предложена Деннисом Зикфуосом в комментарии к коду Свена ниже. При желании это может быть реализовано как альтернативная функция.
  • В настоящее время неясно, как настраивать вложенные разделители контейнеров.
  • Имейте в виду, что цель этой библиотеки – предоставить возможность быстрой установки контейнеров, требующих нулевого кодирования с вашей стороны. Это не универсальная библиотека форматирования, а скорее инструмент разработки, позволяющий облегчить необходимость написания кода котельной для проверки контейнера.

Спасибо всем, кто внес свой вклад!


Примечание. Если вы ищете быстрый способ развертывания пользовательских разделителей, вот один из способов использования стирания стилей. Предположим, что вы уже создали class разделителей, например MyDel , так:

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

Теперь мы хотим написать std::cout << MyPrinter(v) << std::endl; для некоторого контейнера v с использованием этих разделителей. MyPrinter будет classом стирания типа, например:

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

7 Solutions collect form web for “Довольно печатные контейнеры STL STL”

Это решение было вдохновлено решением Marcelo с несколькими изменениями:

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

Как и версия Marcelo, она использует свойство типа is_container, которое должно быть специализированным для всех контейнеров, которые должны поддерживаться. Может быть возможно использовать признак для проверки value_type , const_iterator , begin() / end() , но я не уверен, что рекомендую это, поскольку он может соответствовать тем, которые соответствуют этим критериям, но на самом деле не являются контейнерами, как std::basic_string . Также как и версия Marcelo, она использует шаблоны, которые могут быть специализированы для указания разделителей.

Основное различие заключается в том, что я построил свою версию вокруг pretty_ostream_iterator , которая работает аналогично std::ostream_iterator но не печатает разделитель после последнего элемента. Форматирование контейнеров выполняется с помощью print_container_helper , который может использоваться непосредственно для печати контейнеров без атрибута is_container или для указания другого типа разделителей.

Я также определил is_container и разделители, чтобы он работал для контейнеров с нестандартными предикатами или распределителями, а также для char и wchar_t. Сама функция оператора < < также определена для работы с потоками char и wchar_t.

Наконец, я использовал std::enable_if , который доступен как часть C ++ 0x, и работает в Visual C ++ 2010 и g ++ 4.3 (требуется флаг -std = c ++ 0x) и позже. Таким образом, нет никакой зависимости от Boost.

Это было отредактировано несколько раз, и мы решили назвать основной class, который обертывает коллекцию RangePrinter

Это должно работать автоматически с любой коллекцией после того, как вы написали одноразовый оператор < < перегрузка, за исключением того, что вам понадобится специальный для карт для печати пары, и может захотеть настроить там разделитель.

У вас также может быть специальная функция «печать» для использования в элементе вместо того, чтобы просто выводить его прямо. Подобно алгоритмам STL, вы можете передавать пользовательские предикаты. С картой вы будете использовать ее таким образом, используя специальный принтер для std :: pair.

Ваш принтер по умолчанию просто выведет его в stream.

Хорошо, давайте работать с пользовательским принтером. Я изменю внешний class на RangePrinter. Таким образом, у нас есть 2 iteratorа и некоторые разделители, но не настроены, как печатать фактические элементы.

 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; 

Печать

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

See it on ideone/KKUebZ

6. Code

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

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

The basics are here

Essentially what you do is:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

A pretty printing library:

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

Test code:

 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.

Interesting Posts
Давайте будем гением компьютера.