Tech Off Post

Single Post Permalink

View Thread: Templated STL container pretty printer
  • User profile image
    Sven Groot

    I've been thinking a bit more about this and came up with the following:

    #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.
        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 (default values in container_delimiters.h)
    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;    
    }
    
    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;
    }

    This version is pretty similar to Marcelo's version on SO, but it has a couple of differences.

    Most obviously, it defines and uses a pretty_ostream_iterator that works similar to the regular ostream_iterator, but doesn't print a trailing delimiter. This means that the main printing implementation can use std::copy, which can have an advantage in VC2005 and up. They use checked iterators, and that means that if you loop over the range yourself you'll be doing a bounds check on every iteration. Algorithms like std::copy will often do a single check to see if begin and end are in range, and then use unchecked iterators for the actual loop.

    Of course, pretty_ostream_iterator is also reusable if you want to do more customized formatting of a container.

    There's also a helper functor which you can use to specify a non-standard delimiters type (though unfortunately nested collections will always use the standard delimiters unless you specialize the class specifically for your collection).

    It also uses std::enable_if instead of of boost::enable_if, this is available on VC2010 and g++ 4.3 (if compiling with the -std=c++0x flag), so there's no dependency on boost.

    Finally, all the declarations have been made so it should work with both char and wchar_t, and non-standard allocator and traits types.