打印C ++ STL容器

请注意这篇文章末尾的更新。

更新:我在这个库上为GitHub创建了一个公共项目!


我希望有一个单独的模板,通过operator<< ,一劳永逸地处理所有STL容器。 在伪代码中,我正在寻找像这样的东西:

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

现在我已经在这里看到了很多模板魔法,我从来没有想过可能,所以我想知道是否有人可以提出一些可以匹配所有容器的东西。也许有一些特征可以确定是否有必要的迭代器?

非常感谢!


更新(和解决方案)

在第9频道再次提出这个问题后,我从Sven Groot那里得到了一个很好的答案,它结合了一些SFINAE类型的traiting,似乎以一种完全一般的和可嵌套的方式解决了这个问题。 分隔符可以是单独专用的,包括std :: set的示例专门化,以及使用自定义分隔符的示例。

助手“wrap_array()”可用于打印原始C数组。 更新:对和元组可用于打印; 默认分隔符是圆括号。

enable-if类型特征需要C ++ 0x,但有一些修改后,应该可以创建C ++ 98版本。 元组需要可变参数模板,因此需要C ++ 0x。

我已经要求Sven在这里发布解决方案,以便我可以接受它,但同时我想自己发布代码以供参考。 (更新:Sven现在已经发布了他的代码,我作出了接受的答案。我自己的代码使用容器类型特征,这对我很有用,但可能会导致非容器类提供迭代器的意外行为。)

标题(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

用法示例:

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

进一步的改进想法:

  • 以同样的方式实现std::tuple<...>输出是我们用于std::pair<S,T> 。 更新:现在这是一个单独的问题! Upupdate:现在已经实现了,这要归功于Xeo!
  • 添加名称空间,以便帮助程序类不会流入全局名称空间。 完成
  • 添加模板别名(或类似的东西)以方便制作自定义分隔符类,或者预处理器宏?
  • 最近更新:

  • 我在打印函数中删除了自定义输出迭代器,以支持简单的for循环。
  • 所有的实现细节现在都在pretty_print命名空间中。 只有全局流操作符和pretty_print_array包装器位于全局命名空间中。
  • 修复了命名空间,使operator<<现在在std正确。
  • 笔记:

  • 删除输出迭代器意味着没有办法使用std::copy()来获得漂亮的打印效果。 如果这是一个理想的功能,我可能会恢复漂亮的迭代器,但下面的Sven代码具有实现。
  • 这是一个有意识的设计决定,使分隔符编译时常量而不是对象常量。 这意味着您不能在运行时动态提供分隔符,但这也意味着没有不必要的开销。 Dennis Zickefoose在下面的Sven代码的评论中提出了基于对象的分隔符配置。 如果需要,这可以作为替代特征来实现。
  • 目前不太清楚如何定制嵌套的容器分隔符。
  • 请记住,这个库的目的是允许快速的容器打印设施,你需要零编码。 它不是一个通用的格式化库,而是一个开发工具,用于缓解为容器检测编写锅炉代码的需求。
  • 感谢所有贡献的人!


    注意:如果您正在寻找一种快速部署自定义分隔符的方法,以下是使用类型擦除的一种方法。 我们假设你已经构建了一个分隔符类MyDel ,如下所示:

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

    现在我们希望能够编写std::cout << MyPrinter(v) << std::endl; 对于一些容器v使用这些分隔符。 MyPrinter将是一个类型擦除类,如下所示:

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

    该解决方案受到Marcelo解决方案的启发,并进行了一些更改:

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

    像Marcelo的版本一样,它使用一个is_container类型的特征,它必须专门用于所有需要支持的容器。 有可能使用trait来检查value_typeconst_iteratorbegin() / end() ,但我不确定我会推荐它,因为它可能匹配符合这些条件但实际上不是容器的事物,像std::basic_string 。 同Marcelo的版本一样,它使用可专门指定要使用的分隔符的模板。

    主要区别在于我的版本围绕着pretty_ostream_iterator ,其工作方式类似于std::ostream_iterator但不会在最后一项之后显示分隔符。 格式化容器由print_container_helper完成,它可以直接用于打印没有is_container特征的容器,或指定不同的分隔符类型。

    我还定义了is_container和分隔符,以便它适用于具有非标准谓词或分配器的容器,以及char和wchar_t。 operator <<函数本身也被定义为可以同时使用char和wchar_t流。

    最后,我使用了std::enable_if ,它可以作为C ++ 0x的一部分使用,并且可以在Visual C ++ 2010和g ++ 4.3(需要-std = c ++ 0x标志)以及更高版本中使用。 这种方式对Boost没有依赖性。


    这已被编辑了几次,我们决定调用包装RangePrinter集合的主类

    一旦您编写了一次性操作符<<重载,这应该会自动与任何集合一起工作,除了您需要一个特殊映射来打印该对,并且可能需要在其中自定义分隔符。

    您也可以在项目上使用特殊的“打印”功能,而不是直接输出。 有点像STL算法允许你传入自定义谓词。 有了地图,你可以用这种方式使用它,使用std :: pair的自定义打印机。

    您的“默认”打印机只会将其输出到流中。

    好的,让我们来研究一下自定义打印机。 我会将我的外部类更改为RangePrinter。 所以我们有2个迭代器和一些分隔符,但没有定制如何打印实际项目。

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

    现在,默认情况下,只要键和值类型都是可打印的,您就可以在自己的特殊物品打印机中放置地图(如同您可以使用任何其他类型),或者如果您不想=作为分隔符。

    我正在将自由函数移动到现在的最后:

    一个自由函数(迭代器版本)看起来像这样,你甚至可以有默认值:

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

    然后,您可以将它用于std :: set by

     std::cout << outputFormatter( mySet );
    

    您也可以编写自定义打印机的自由功能版本和采用两个迭代器的版本。 在任何情况下,他们都会为您解析模板参数,并且您可以将它们作为临时对象传递。


    这是一个工作图书馆,作为一个完整的工作程序呈现,我只是一起黑了:

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

    它目前仅适用于vectorset ,但可以通过扩展IsContainer专业化来使用大多数容器。 我对这个代码是否是最小的问题并没有多少考虑,但我不能立即想到任何我可以删除的冗余。

    编辑:只是踢,我包括处理数组的版本。 我不得不排除char数组以避免模糊性; 它可能仍然会遇到wchar_t[]

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

    上一篇: print C++ STL containers

    下一篇: Use 'class' or 'typename' for template parameters?