print C++ STL containers

Please take note of the updates at the end of this post.

Update: I have created a public project on GitHub for this library!


I would like to have a single template that once and for all takes care of pretty-printing all STL containers via operator<< . In pseudo code, I'm looking for something like this:

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

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?

Many thanks!


Update (and solution)

After raising this problem again on Channel 9, 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.

The helper "wrap_array()" can be used to print raw C arrays. Update: Pairs and tuples are available for printing; default delimiters are round brackets.

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.

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. (Update: 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.)

Header (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

Usage example:

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

Further ideas for improvements:

  • Implement output for std::tuple<...> in the same way is we have it for std::pair<S,T> . Update: This is now a separate question on SO! Upupdate: This has now been implemented, thanks to Xeo!
  • Add namespaces so that the helper classes don't bleed into the global namespace. Done
  • Add template aliases (or something similar) to facilitate making custom delimiter classes, or maybe preprocessor macros?
  • Recent updates:

  • I removed the custom output iterator in favour of a simple for loop in the print function.
  • All implementation details are now in the pretty_print namespace. Only the global stream operators and the pretty_print_array wrapper are in the global namespace.
  • Fixed the namespacing so that operator<< is now correctly in std .
  • Notes:

  • Removing the output iterator means that there is no way to use std::copy() to get pretty-printing. I might reinstate the pretty iterator if this is a desired feature, but Sven's code below has the implementation.
  • 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.
  • It is currently not obvious how to customize nested container delimiters.
  • Bear in mind that the purpose of this library is to allow quick container printing facilities that require zero coding 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.
  • Thank you to everyone who contributed!


    Note: 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 MyDel , like so:

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

    Now we want to be able to write std::cout << MyPrinter(v) << std::endl; for some container v using those delimiters. MyPrinter will be a type-erasing class, like so:

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

    This solution was inspired by Marcelo's solution, with a few changes:

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

    Like Marcelo's version, it uses an is_container type trait that must be specialized for all containers that are to be supported. It may be possible to use a trait to check for value_type , const_iterator , begin() / end() , but I'm not sure I'd recommend that since it might match things that match those criteria but aren't actually containers, like std::basic_string . Also like Marcelo's version, it uses templates that can be specialized to specify the delimiters to use.

    The major difference is that I've built my version around a pretty_ostream_iterator , which works similar to the std::ostream_iterator but doesn't print a delimiter after the last item. Formatting the containers is done by the print_container_helper , which can be used directly to print containers without an is_container trait, or to specify a different delimiters type.

    I've also defined is_container and delimiters so it will work for containers with non-standard predicates or allocators, and for both char and wchar_t. The operator<< function itself is also defined to work with both char and wchar_t streams.

    Finally, I've used std::enable_if , which is available as part of C++0x, and works in Visual C++ 2010 and g++ 4.3 (needs the -std=c++0x flag) and later. This way there is no dependency on Boost.


    This has been edited a few times, and we have decided to call the main class that wraps a collection RangePrinter

    This should work automatically with any collection once you have written the one-time operator<< overload, except that you will need a special one for maps to print the pair, and may want to customise the delimiter there.

    You could also have a special "print" function to use on the item instead of just outputting it direct. A bit like STL algorithms allow you to pass in custom predicates. With map you would use it this way, with a custom printer for the std::pair.

    Your "default" printer would just output it to the stream.

    Ok, let's work on a custom printer. I will change my outer class to RangePrinter. So we have 2 iterators and some delimiters but have not customised how to print the actual items.

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

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

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

    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[] .

    链接地址: http://www.djcxy.com/p/6334.html

    上一篇: 我如何关机

    下一篇: 打印C ++ STL容器