Note that there are some explanatory texts on larger screens.

plurals
  1. POPretty-print C++ STL containers
    text
    copied!<p><em>Please take note of the updates at the end of this post.</em></p> <p><strong>Update: I have created a <a href="http://louisdx.github.com/cxx-prettyprint/" rel="noreferrer">public project on GitHub</a> for this library!</strong></p> <hr> <p>I would like to have a single template that once and for all takes care of pretty-printing all STL containers via <code>operator&lt;&lt;</code>. In pseudo code, I'm looking for something like this:</p> <pre><code>template&lt;container C, class T, String delim = ", ", String open = "[", String close = "]"&gt; std::ostream &amp; operator&lt;&lt;(std::ostream &amp; o, const C&lt;T&gt; &amp; x) { o &lt;&lt; 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 &lt;&lt; delim; o &lt;&lt; *i; } o &lt;&lt; close; return o; } </code></pre> <p>Now I've seen plenty of template magic here on SO that I never thought possible, so I'm wondering if anyone can suggest something that would match all containers C. Maybe something trait-ish that can figure out if something has the necessary iterator?</p> <p>Many thanks!</p> <hr> <p><strong>Update (and solution)</strong></p> <p>After raising this problem again on <a href="http://channel9.msdn.com/Forums/TechOff/Templated-STL-container-pretty-printer" rel="noreferrer">Channel 9</a>, I got a fantastic answer from Sven Groot, which, combined with a bit of SFINAE type traiting, appears to solve the problem in a completely general and nestable fashion. The delimiters may be individually specialised, an example specialization for std::set is included, as well as an example of using custom delimiters.</p> <p>The helper "wrap_array()" can be used to print raw C arrays. <em>Update:</em> Pairs and tuples are available for printing; default delimiters are round brackets.</p> <p>The enable-if type trait requires C++0x, but with some modifications it should be possible to make a C++98 version of this. Tuples require variadic templates, hence C++0x.</p> <p>I have asked Sven to post the solution here so that I can accept it, but in the meantime I'd like to post the code myself for reference. (<em>Update:</em> Sven has now posted his code below, which I made the accepted answer. My own code uses container type traits, which work for me but may cause unexpected behaviour with non-container classes that provide iterators.)</p> <p><em>Header (prettyprint.h):</em></p> <pre><code>#ifndef H_PRETTY_PRINT #define H_PRETTY_PRINT #include &lt;type_traits&gt; #include &lt;iostream&gt; #include &lt;utility&gt; #include &lt;tuple&gt; 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&lt;typename T, typename TTraits, typename TAllocator&gt; 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&lt;typename T&gt; struct is_container_helper { private: template&lt;typename C&gt; static char test(typename C::const_iterator*); template&lt;typename C&gt; static int test(...); public: static const bool value = sizeof(test&lt;T&gt;(0)) == sizeof(char); }; // Basic is_container template; specialize to derive from std::true_type for all desired container types template&lt;typename T&gt; struct is_container : public ::std::integral_constant&lt;bool, is_container_helper&lt;T&gt;::value&gt; { }; // Holds the delimiter values for a specific character type template&lt;typename TChar&gt; 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&lt;typename T, typename TChar&gt; struct delimiters { typedef delimiters_values&lt;TChar&gt; type; static const type values; }; // Default delimiters template&lt;typename T&gt; struct delimiters&lt;T, char&gt; { static const delimiters_values&lt;char&gt; values; }; template&lt;typename T&gt; const delimiters_values&lt;char&gt; delimiters&lt;T, char&gt;::values = { "[", ", ", "]" }; template&lt;typename T&gt; struct delimiters&lt;T, wchar_t&gt; { static const delimiters_values&lt;wchar_t&gt; values; }; template&lt;typename T&gt; const delimiters_values&lt;wchar_t&gt; delimiters&lt;T, wchar_t&gt;::values = { L"[", L", ", L"]" }; // Delimiters for set template&lt;typename T, typename TTraits, typename TAllocator&gt; struct delimiters&lt; ::std::set&lt;T, TTraits, TAllocator&gt;, char&gt; { static const delimiters_values&lt;char&gt; values; }; template&lt;typename T, typename TTraits, typename TAllocator&gt; const delimiters_values&lt;char&gt; delimiters&lt; ::std::set&lt;T, TTraits, TAllocator&gt;, char&gt;::values = { "{", ", ", "}" }; template&lt;typename T, typename TTraits, typename TAllocator&gt; struct delimiters&lt; ::std::set&lt;T, TTraits, TAllocator&gt;, wchar_t&gt; { static const delimiters_values&lt;wchar_t&gt; values; }; template&lt;typename T, typename TTraits, typename TAllocator&gt; const delimiters_values&lt;wchar_t&gt; delimiters&lt; ::std::set&lt;T, TTraits, TAllocator&gt;, wchar_t&gt;::values = { L"{", L", ", L"}" }; // Delimiters for pair (reused for tuple, see below) template&lt;typename T1, typename T2&gt; struct delimiters&lt; ::std::pair&lt;T1, T2&gt;, char&gt; { static const delimiters_values&lt;char&gt; values; }; template&lt;typename T1, typename T2&gt; const delimiters_values&lt;char&gt; delimiters&lt; ::std::pair&lt;T1, T2&gt;, char&gt;::values = { "(", ", ", ")" }; template&lt;typename T1, typename T2&gt; struct delimiters&lt; ::std::pair&lt;T1, T2&gt;, wchar_t&gt; { static const delimiters_values&lt;wchar_t&gt; values; }; template&lt;typename T1, typename T2&gt; const delimiters_values&lt;wchar_t&gt; delimiters&lt; ::std::pair&lt;T1, T2&gt;, wchar_t&gt;::values = { L"(", L", ", L")" }; // Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type. template&lt;typename T, typename TChar = char, typename TCharTraits = ::std::char_traits&lt;TChar&gt;, typename TDelimiters = delimiters&lt;T, TChar&gt;&gt; struct print_container_helper { typedef TChar char_type; typedef TDelimiters delimiters_type; typedef std::basic_ostream&lt;TChar, TCharTraits&gt; &amp; ostream_type; print_container_helper(const T &amp; container) : _container(container) { } inline void operator()(ostream_type &amp; stream) const { if (delimiters_type::values.prefix != NULL) stream &lt;&lt; delimiters_type::values.prefix; for (typename T::const_iterator beg = _container.begin(), end = _container.end(), it = beg; it != end; ++it) { if (it != beg &amp;&amp; delimiters_type::values.delimiter != NULL) stream &lt;&lt; delimiters_type::values.delimiter; stream &lt;&lt; *it; } if (delimiters_type::values.postfix != NULL) stream &lt;&lt; delimiters_type::values.postfix; } private: const T &amp; _container; }; // Type-erasing helper class for easy use of custom delimiters. // Requires TCharTraits = std::char_traits&lt;TChar&gt; and TChar = char or wchar_t, and MyDelims needs to be defined for TChar. // Usage: "cout &lt;&lt; pretty_print::custom_delims&lt;MyDelims&gt;(x)". struct custom_delims_base { virtual ~custom_delims_base() { } virtual ::std::ostream &amp; stream(::std::ostream &amp;) = 0; virtual ::std::wostream &amp; stream(::std::wostream &amp;) = 0; }; template &lt;typename T, typename Delims&gt; struct custom_delims_wrapper : public custom_delims_base { custom_delims_wrapper(const T &amp; t) : t(t) { } ::std::ostream &amp; stream(::std::ostream &amp; stream) { return stream &lt;&lt; ::pretty_print::print_container_helper&lt;T, char, ::std::char_traits&lt;char&gt;, Delims&gt;(t); } ::std::wostream &amp; stream(::std::wostream &amp; stream) { return stream &lt;&lt; ::pretty_print::print_container_helper&lt;T, wchar_t, ::std::char_traits&lt;wchar_t&gt;, Delims&gt;(t); } private: const T &amp; t; }; template &lt;typename Delims&gt; struct custom_delims { template &lt;typename Container&gt; custom_delims(const Container &amp; c) : base(new custom_delims_wrapper&lt;Container, Delims&gt;(c)) { } ~custom_delims() { delete base; } custom_delims_base * base; }; } // namespace pretty_print template &lt;typename TChar, typename TCharTraits, typename Delims&gt; inline std::basic_ostream&lt;TChar, TCharTraits&gt; &amp; operator&lt;&lt;(std::basic_ostream&lt;TChar, TCharTraits&gt; &amp; stream, const pretty_print::custom_delims&lt;Delims&gt; &amp; p) { return p.base-&gt;stream(stream); } // Template aliases for char and wchar_t delimiters // Enable these if you have compiler support // // Implement as "template&lt;T, C, A&gt; const sdelims::type sdelims&lt;std::set&lt;T,C,A&gt;&gt;::values = { ... }." //template&lt;typename T&gt; using pp_sdelims = pretty_print::delimiters&lt;T, char&gt;; //template&lt;typename T&gt; using pp_wsdelims = pretty_print::delimiters&lt;T, wchar_t&gt;; namespace std { // Prints a print_container_helper to the specified stream. template&lt;typename T, typename TChar, typename TCharTraits, typename TDelimiters&gt; inline basic_ostream&lt;TChar, TCharTraits&gt; &amp; operator&lt;&lt;(basic_ostream&lt;TChar, TCharTraits&gt; &amp; stream, const ::pretty_print::print_container_helper&lt;T, TChar, TCharTraits, TDelimiters&gt; &amp; helper) { helper(stream); return stream; } // Prints a container to the stream using default delimiters template&lt;typename T, typename TChar, typename TCharTraits&gt; inline typename enable_if&lt; ::pretty_print::is_container&lt;T&gt;::value, basic_ostream&lt;TChar, TCharTraits&gt;&amp;&gt;::type operator&lt;&lt;(basic_ostream&lt;TChar, TCharTraits&gt; &amp; stream, const T &amp; container) { return stream &lt;&lt; ::pretty_print::print_container_helper&lt;T, TChar, TCharTraits&gt;(container); } // Prints a pair to the stream using delimiters from delimiters&lt;std::pair&lt;T1, T2&gt;&gt;. template&lt;typename T1, typename T2, typename TChar, typename TCharTraits&gt; inline basic_ostream&lt;TChar, TCharTraits&gt; &amp; operator&lt;&lt;(basic_ostream&lt;TChar, TCharTraits&gt; &amp; stream, const pair&lt;T1, T2&gt; &amp; value) { if (::pretty_print::delimiters&lt;pair&lt;T1, T2&gt;, TChar&gt;::values.prefix != NULL) stream &lt;&lt; ::pretty_print::delimiters&lt;pair&lt;T1, T2&gt;, TChar&gt;::values.prefix; stream &lt;&lt; value.first; if (::pretty_print::delimiters&lt;pair&lt;T1, T2&gt;, TChar&gt;::values.delimiter != NULL) stream &lt;&lt; ::pretty_print::delimiters&lt;pair&lt;T1, T2&gt;, TChar&gt;::values.delimiter; stream &lt;&lt; value.second; if (::pretty_print::delimiters&lt;pair&lt;T1, T2&gt;, TChar&gt;::values.postfix != NULL) stream &lt;&lt; ::pretty_print::delimiters&lt;pair&lt;T1, T2&gt;, TChar&gt;::values.postfix; return stream; } } // namespace std // Prints a tuple to the stream using delimiters from delimiters&lt;std::pair&lt;tuple_dummy_t, tuple_dummy_t&gt;&gt;. namespace pretty_print { struct tuple_dummy_t { }; // Just if you want special delimiters for tuples. typedef std::pair&lt;tuple_dummy_t, tuple_dummy_t&gt; tuple_dummy_pair; template&lt;typename Tuple, size_t N, typename TChar, typename TCharTraits&gt; struct pretty_tuple_helper { static inline void print(::std::basic_ostream&lt;TChar, TCharTraits&gt; &amp; stream, const Tuple &amp; value) { pretty_tuple_helper&lt;Tuple, N - 1, TChar, TCharTraits&gt;::print(stream, value); if (delimiters&lt;tuple_dummy_pair, TChar&gt;::values.delimiter != NULL) stream &lt;&lt; delimiters&lt;tuple_dummy_pair, TChar&gt;::values.delimiter; stream &lt;&lt; std::get&lt;N - 1&gt;(value); } }; template&lt;typename Tuple, typename TChar, typename TCharTraits&gt; struct pretty_tuple_helper&lt;Tuple, 1, TChar, TCharTraits&gt; { static inline void print(::std::basic_ostream&lt;TChar, TCharTraits&gt; &amp; stream, const Tuple &amp; value) { stream &lt;&lt; ::std::get&lt;0&gt;(value); } }; } // namespace pretty_print namespace std { template&lt;typename TChar, typename TCharTraits, typename ...Args&gt; inline basic_ostream&lt;TChar, TCharTraits&gt; &amp; operator&lt;&lt;(basic_ostream&lt;TChar, TCharTraits&gt; &amp; stream, const tuple&lt;Args...&gt; &amp; value) { if (::pretty_print::delimiters&lt; ::pretty_print::tuple_dummy_pair, TChar&gt;::values.prefix != NULL) stream &lt;&lt; ::pretty_print::delimiters&lt; ::pretty_print::tuple_dummy_pair, TChar&gt;::values.prefix; ::pretty_print::pretty_tuple_helper&lt;const tuple&lt;Args...&gt; &amp;, sizeof...(Args), TChar, TCharTraits&gt;::print(stream, value); if (::pretty_print::delimiters&lt; ::pretty_print::tuple_dummy_pair, TChar&gt;::values.postfix != NULL) stream &lt;&lt; ::pretty_print::delimiters&lt; ::pretty_print::tuple_dummy_pair, TChar&gt;::values.postfix; return stream; } } // namespace std // A wrapper for raw C-style arrays. Usage: int arr[] = { 1, 2, 4, 8, 16 }; std::cout &lt;&lt; wrap_array(arr) &lt;&lt; ... namespace pretty_print { template &lt;typename T, size_t N&gt; struct array_wrapper { typedef const T * const_iterator; typedef T value_type; array_wrapper(const T (&amp; 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 &lt;typename T, size_t N&gt; inline pretty_print::array_wrapper&lt;T, N&gt; pretty_print_array(const T (&amp; a)[N]) { return pretty_print::array_wrapper&lt;T, N&gt;(a); } #endif </code></pre> <p><em>Usage example:</em></p> <pre><code>#include &lt;iostream&gt; #include &lt;vector&gt; #include &lt;unordered_map&gt; #include &lt;map&gt; #include &lt;set&gt; #include &lt;array&gt; #include &lt;tuple&gt; #include &lt;utility&gt; #include &lt;string&gt; #include "prettyprint.h" // Specialization for a particular container template&lt;&gt; const pretty_print::delimiters_values&lt;char&gt; pretty_print::delimiters&lt;std::vector&lt;double&gt;, char&gt;::values = { "|| ", " : ", " ||" }; // Custom delimiters for one-off use struct MyDel { static const delimiters_values&lt;char&gt; values; }; const delimiters_values&lt;char&gt; MyDel::values = { "&lt;", "; ", "&gt;" }; int main(int argc, char * argv[]) { std::string cs; std::unordered_map&lt;int, std::string&gt; um; std::map&lt;int, std::string&gt; om; std::set&lt;std::string&gt; ss; std::vector&lt;std::string&gt; v; std::vector&lt;std::vector&lt;std::string&gt;&gt; vv; std::vector&lt;std::pair&lt;int, std::string&gt;&gt; vp; std::vector&lt;double&gt; vd; v.reserve(argc - 1); vv.reserve(argc - 1); vp.reserve(argc - 1); vd.reserve(argc - 1); std::cout &lt;&lt; "Printing pairs." &lt;&lt; std::endl; while (--argc) { std::string s(argv[argc]); std::pair&lt;int, std::string&gt; 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 &lt;&lt; " " &lt;&lt; p &lt;&lt; std::endl; } std::array&lt;char, 5&gt; a{{ 'h', 'e', 'l', 'l', 'o' }}; std::cout &lt;&lt; "Vector: " &lt;&lt; v &lt;&lt; std::endl &lt;&lt; "Incremental vector: " &lt;&lt; vv &lt;&lt; std::endl &lt;&lt; "Another vector: " &lt;&lt; vd &lt;&lt; std::endl &lt;&lt; "Pairs: " &lt;&lt; vp &lt;&lt; std::endl &lt;&lt; "Set: " &lt;&lt; ss &lt;&lt; std::endl &lt;&lt; "OMap: " &lt;&lt; om &lt;&lt; std::endl &lt;&lt; "UMap: " &lt;&lt; um &lt;&lt; std::endl &lt;&lt; "String: " &lt;&lt; cs &lt;&lt; std::endl &lt;&lt; "Array: " &lt;&lt; a &lt;&lt; std::endl ; // Using custom delimiters manually: std::cout &lt;&lt; pretty_print::print_container_helper&lt;std::vector&lt;std::string&gt;, char, std::char_traits&lt;char&gt;, MyDel&gt;(v) &lt;&lt; std::endl; // Using custom delimiters with the type-erasing helper class std::cout &lt;&lt; pretty_print::custom_delims&lt;MyDel&gt;(v) &lt;&lt; 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&lt;double, std::string&gt;(1.5, "meow")); int arr[] = { 1, 4, 9, 16 }; std::cout &lt;&lt; "C array: " &lt;&lt; wrap_array(arr) &lt;&lt; std::endl &lt;&lt; "Pair: " &lt;&lt; a1 &lt;&lt; std::endl &lt;&lt; "1-tuple: " &lt;&lt; a2 &lt;&lt; std::endl &lt;&lt; "n-tuple: " &lt;&lt; a3 &lt;&lt; std::endl &lt;&lt; "n-tuple: " &lt;&lt; a4 &lt;&lt; std::endl ; } </code></pre> <p><em>Further ideas for improvements:</em></p> <ul> <li><s>Implement output for <code>std::tuple&lt;...&gt;</code> in the same way is we have it for <code>std::pair&lt;S,T&gt;</code>.</s> <em>Update:</em> This is now a <a href="https://stackoverflow.com/questions/6245735/pretty-print-stdtuple">separate question on SO</a>! <em>Upupdate:</em> This has now been implemented, thanks to Xeo!</li> <li><s>Add namespaces so that the helper classes don't bleed into the global namespace.</s> <em>Done</em></li> <li>Add template aliases (or something similar) to facilitate making custom delimiter classes, or maybe preprocessor macros?</li> </ul> <p><em>Recent updates:</em></p> <ul> <li>I removed the custom output iterator in favour of a simple for loop in the print function.</li> <li>All implementation details are now in the <code>pretty_print</code> namespace. Only the global stream operators and the <code>pretty_print_array</code> wrapper are in the global namespace.</li> <li>Fixed the namespacing so that <code>operator&lt;&lt;</code> is now correctly in <code>std</code>.</li> </ul> <p><em>Notes:</em></p> <ul> <li>Removing the output iterator means that there is no way to use <code>std::copy()</code> to get pretty-printing. I might reinstate the pretty iterator if this is a desired feature, but Sven's code below has the implementation.</li> <li>It was a conscious design decision to make the delimiters compile-time constants rather than object constants. That means that you cannot supply delimiters dynamically at runtime, but it also means that there's no unneeded overhead. An object-based delimiter configuration has been proposed by Dennis Zickefoose in a comment to Sven's code below. If desired, this could be implemented as an alternative feature.</li> <li>It is currently not obvious how to customize nested container delimiters.</li> <li>Bear in mind that the purpose of this library is to allow <em>quick</em> container printing facilities that require <em>zero coding</em> on your part. It is not an all-purpose formatting library, but rather a developing tool to alleviate the need to write boiler-plate code for container inspection.</li> </ul> <p><em>Thank you to everyone who contributed!</em></p> <hr> <p><em>Note:</em> If you are looking for a quick way to deploy custom delimiters, here is one way using type erasure. We assume that you have already constructed a delimiter class, say <code>MyDel</code>, like so:</p> <pre><code>struct MyDel { static const pretty_print::delimiters_values&lt;char&gt; values; }; const pretty_print::delimiters_values&lt;char&gt; MyDel::values = { "&lt;", "; ", "&gt;" }; </code></pre> <p>Now we want to be able to write <code>std::cout &lt;&lt; MyPrinter(v) &lt;&lt; std::endl;</code> for some container <code>v</code> using those delimiters. <code>MyPrinter</code> will be a type-erasing class, like so:</p> <pre><code>struct wrapper_base { virtual ~wrapper_base() { } virtual std::ostream &amp; stream(std::ostream &amp; o) = 0; }; template &lt;typename T, typename Delims&gt; struct wrapper : public wrapper_base { wrapper(const T &amp; t) : t(t) { } std::ostream &amp; stream(std::ostream &amp; o) { return o &lt;&lt; pretty_print::print_container_helper&lt;T, char, std::char_traits&lt;char&gt;, Delims&gt;(t); } private: const T &amp; t; }; template &lt;typename Delims&gt; struct MyPrinter { template &lt;typename Container&gt; MyPrinter(const Container &amp; c) : base(new wrapper&lt;Container, Delims&gt;(c)) { } ~MyPrinter() { delete base; } wrapper_base * base; }; template &lt;typename Delims&gt; std::ostream &amp; operator&lt;&lt;(std::ostream &amp; o, const MyPrinter&lt;Delims&gt; &amp; p) { return p.base-&gt;stream(o); } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload