首页 文章

漂亮的印刷C STL容器

提问于
浏览
347

请注意本文末尾的更新 .

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


我希望有一个模板,一劳永逸地通过 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;
}

现在我已经在SO上看到了很多我从未想过可能的模板魔法,所以我想知道是否有人可以建议一些能匹配所有容器C的东西 . 也许某些特性可以判断出某些东西是否有必要的迭代器?

非常感谢!


Update (and solution)

Channel 9上再次提出这个问题之后,我从Sven Groot得到了一个很棒的答案,结合了一点SFINAE类型的特征,似乎以一种完全通用和可嵌套的方式解决了这个问题 . 分隔符可以是单独的专用,包括std :: set的示例特化,以及使用自定义分隔符的示例 .

帮助程序"wrap_array()"可用于打印原始C数组 . 更新:对和元组可用于打印;默认分隔符是圆括号 .

enable-if类型特征需要C 0x,但经过一些修改后,应该可以制作C 98版本 . 元组需要可变参数模板,因此需要C 0x .

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

Headers (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 :: pair <S,T>相同的方式实现std :: tuple <...>的输出 . 更新:现在是separate question on SO! Upupdate:感谢Xeo,现在已经实现了!

  • 添加名称空间,以便辅助类不会渗透到全局名称空间中 . 完成

  • 添加模板别名(或类似的东西)以便于制作自定义分隔符类,或者可能是预处理器宏?

最近更新:

  • 我删除了自定义输出迭代器,转而使用print函数中的简单for循环 .

  • 所有实现细节现在都在 pretty_print 命名空间中 . 只有全局流运算符和 pretty_print_array 包装器位于全局命名空间中 .

  • 修复了命名空间,以便 operator<< 现在正确地位于 std 中 .

笔记:

  • 删除输出迭代器意味着无法使用 std::copy() 进行漂亮的打印 . 如果这是一个理想的功能,我可能会恢复漂亮的迭代器,但下面的Sven代码有实现 .

  • 这是一个有意识的设计决定,使分隔符编译时常量而不是对象常量 . 这意味着您无法在运行时动态提供分隔符,但这也意味着下面有's no unneeded overhead. An object-based delimiter configuration has been proposed by Dennis Zickefoose in a comment to Sven'代码 . 如果需要,这可以作为替代功能实现 .

  • 目前尚不清楚如何自定义嵌套容器分隔符 .

  • 请记住,此库的目的是允许快速容器打印设备,您需要零编码 . 它不是一个通用的格式化库,而是一个开发工具,以减少编写容器检查的样板代码的需要 .

谢谢所有贡献的人!


注意:如果您正在寻找一种快速部署自定义分隔符的方法,这里有一种使用类型擦除的方法 . 我们假设你已经构造了一个分隔符类,比如 MyDel ,就像这样:

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

现在我们希望能够使用这些分隔符为某个容器 v 编写 std::cout << MyPrinter(v) << std::endl; . 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); }

7 回答

  • 5

    这是一个工作库,作为一个完整的工作程序,我刚刚一起攻击:

    #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 特化,即可与大多数容器一起使用 . 我没有立即想到任何我可以删除多余的东西 .

    EDIT: 仅仅是因为踢,我包括一个处理数组的版本 . 我不得不排除char数组以避免进一步的模糊; wchar_t[] 可能仍会遇到麻烦 .

  • 2

    我将在这里添加另一个答案,因为我已经提出了与前一个方法不同的方法,那就是使用区域设置方面 .

    基础是here

    基本上你做的是:

    • 创建一个派生自 std::locale::facet 的类 . 稍微缺点是你需要一个编译单元来保存它的id . 让's call it MyPrettyVectorPrinter. You' d可能给它一个更好的名字,并为对和 Map 创建一个 .

    • 在您的信息流功能中,您检查 std::has_facet< MyPrettyVectorPrinter >

    • 如果返回true,则使用 std::use_facet< MyPrettyVectorPrinter >( os.getloc() ) 将其解压缩

    • 您的facet对象将具有分隔符的值,您可以阅读它们 . 如果方面不是发现,您的打印功能( operator<< )提供默认值 . 请注意,您可以为读取矢量执行相同的操作 .

    我喜欢这种方法,因为您可以使用默认打印,同时仍然可以使用自定义覆盖 .

    如果在多个项目中使用(不仅仅是 Headers ),还需要注意创建新语言环境对象的费用这一事实的缺点是需要一个库 .

    我把它写成一个新的解决方案,而不是修改我的另一个,因为我相信这两种方法都是正确的,你可以选择 .

  • 1

    我的解决方案是simple.h,它是scc包的一部分 . 所有标准容器, Map ,集合,c阵列都是可打印的 .

  • 7

    这个解决方案的灵感来自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类型特征,该特征必须专用于所有要支持的容器 . 也许可以使用特征来检查 value_typeconst_iteratorbegin() / end() ,但我建议,因为它可能匹配符合这些条件但实际上不是容器的东西,例如 std::basic_string . 与Marcelo的版本一样,它使用专门用于指定要使用的分隔符的模板 .

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

    我还定义了is_container和delimiters,因此它适用于具有非标准谓词或分配器的容器,以及char和wchar_t . operator <<函数本身也被定义为与char和wchar_t流一起使用 .

    最后,我使用了 std::enable_if ,它作为C 0x的一部分提供,适用于Visual C 2010和g 4.3(需要-std = c 0x标志)及更高版本 . 这种方式不依赖于Boost .

  • 21

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

    一旦编写了一次性操作符<< overload,这应该可以自动与任何集合一起使用,除了您需要一个特殊的映射来打印该对,并且可能想要在那里自定义分隔符 .

    您还可以在项目上使用特殊的“打印”功能,而不是直接输出 . 有点像STL算法允许您传递自定义谓词 . 使用map,您可以使用这种方式,使用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;
    }
    

    现在默认情况下它只适用于 Map ,只要键和值类型都是可打印的,你可以放入你自己的特殊项目打印机,当它们不是(你可以使用任何其他类型),或者如果你不想要=作为分隔符 .

    我正在移动自由函数来创建这些到现在为止:

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

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

    您还可以编写采用自定义打印机的自由功能版本和采用两个迭代器的自由功能版本 . 在任何情况下,他们都会为您解析模板参数,您将能够将它们作为临时文件传递出去 .

  • 75

    这里的目标是使用ADL来定制我们如何打印 .

    传入格式化程序标记,并覆盖标记命名空间中的4个函数(之前,之后,之间和之后) . 这会改变格式化程序在迭代容器时打印“装饰”的方式 .

    一个默认格式化程序,用于映射 {(a->b),(c->d)} ,用于元组的 (a,b,c) ,用于字符串的 "hello" ,用于包含其他所有内容的 [x,y,z] .

    它应该“正常工作”第三方可迭代类型(并将它们视为“其他所有”) .

    如果您想要第三方迭代的自定义装饰,只需创建自己的标签即可 . 处理 Map 下降需要一些工作(您需要重载 pretty_print_descend( your_tag 以返回 pretty_print::decorator::map_magic_tag<your_tag> ) . 也许有一种更清洁的方法来做到这一点,不确定 .

    一个小库来检测迭代性和元组:

    namespace details {
      using std::begin; using std::end;
      template<class T, class=void>
      struct is_iterable_test:std::false_type{};
      template<class T>
      struct is_iterable_test<T,
        decltype((void)(
          (void)(begin(std::declval<T>())==end(std::declval<T>()))
          , ((void)(std::next(begin(std::declval<T>()))))
          , ((void)(*begin(std::declval<T>())))
          , 1
        ))
      >:std::true_type{};
      template<class T>struct is_tupleoid:std::false_type{};
      template<class...Ts>struct is_tupleoid<std::tuple<Ts...>>:std::true_type{};
      template<class...Ts>struct is_tupleoid<std::pair<Ts...>>:std::true_type{};
      // template<class T, size_t N>struct is_tupleoid<std::array<T,N>>:std::true_type{}; // complete, but problematic
    }
    template<class T>struct is_iterable:details::is_iterable_test<std::decay_t<T>>{};
    template<class T, std::size_t N>struct is_iterable<T(&)[N]>:std::true_type{}; // bypass decay
    template<class T>struct is_tupleoid:details::is_tupleoid<std::decay_t<T>>{};
    
    template<class T>struct is_visitable:std::integral_constant<bool, is_iterable<T>{}||is_tupleoid<T>{}> {};
    

    一个允许我们访问可迭代或元组类型对象内容的库:

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

    一个漂亮的打印库:

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

    测试代码:

    int main() {
      std::vector<int> x = {1,2,3};
    
      pretty_print::print( std::cout, x );
      std::cout << "\n";
    
      std::map< std::string, int > m;
      m["hello"] = 3;
      m["world"] = 42;
    
      pretty_print::print( std::cout, m );
      std::cout << "\n";
    }
    

    live example

    这确实使用了C 14特性(一些 _t 别名和 auto&& lambdas),但没有一个是必不可少的 .

  • 15

    这个代码现在已经好几次被证明是有用的了,我觉得进入定制的费用非常低 . 因此,我决定在 MIT 许可下发布它,并提供一个GitHub存储库,其中可以下载 Headers 和一个小示例文件 .

    http://djmuw.github.io/prettycc

    0.前言和措辞

    根据此答案, 'decoration' 是一组前缀字符串,分隔符字符串和后缀字符串 . 前缀字符串插入到流之前和后缀字符串之后的容器值(请参阅2.目标容器) . 分隔符字符串插入相应容器的值之间 .

    注意:实际上,这个答案没有解决100%的问题,因为装饰不是严格编译的时间常数,因为需要运行时检查来检查自定义装饰是否已应用于当前流 . 不过,我认为它有一些不错的功能 .

    注2:可能有小错误,因为它尚未经过充分测试 .

    1.一般想法/用法

    使用所需的零附加代码

    它应该像保持一样简单

    #include <vector>
    #include "pretty.h"
    
    int main()
    {
      std::cout << std::vector<int>{1,2,3,4,5}; // prints 1, 2, 3, 4, 5
      return 0;
    }
    

    轻松定制......

    ...关于特定流对象

    #include <vector>
    #include "pretty.h"
    
    int main()
    {
      // set decoration for std::vector<int> for cout object
      std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
      std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
      return 0;
    }
    

    或者对于所有流:

    #include <vector>
    #include "pretty.h"
    
    // set decoration for std::vector<int> for all ostream objects
    PRETTY_DEFAULT_DECORATION(std::vector<int>, "{", ", ", "}")
    
    int main()
    {
      std::cout << std::vector<int>{1,2,3,4,5}; // prints {1, 2, 3, 4, 5}
      std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
      std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
      return 0;
    }
    

    粗略描述

    • 该代码包括一个类模板,为任何类型提供默认装饰

    • 可以专门用于更改(a)某些类型的默认装饰,它是

    • 使用 ios_base 提供的私有存储使用 xalloc / pword ,以便保存指向 pretty::decor 对象的指针,该对象专门装饰某个流上的某种类型 .

    如果未明确设置此流的 pretty::decor<T> 对象,则调用 pretty::defaulted<T, charT, chartraitT>::decoration() 以获取给定类型的默认装饰 . 类 pretty::defaulted 将专门用于自定义默认装饰 .

    2.目标对象/容器

    此代码的'pretty decoration'的目标对象 obj 是具有其中任何一个的对象

    • 重载 std::beginstd::end 已定义(包括C样式数组),

    • 通过ADL提供 begin(obj)end(obj)

    • 的类型 std::tuple

    • 或类型 std::pair .

    该代码包括用于识别具有范围特征的类的特征( begin / end ) . (但是没有包括检查, begin(obj) == end(obj) 是否是一个有效的表达式 . )

    该代码在全局命名空间中提供 operator<< ,仅适用于没有更专用的 operator<< 版本的类 . 因此,例如,虽然具有有效的 begin / end 对,但在此代码中不使用运算符打印 std::string .

    3.利用率和自定义

    可以为每种类型(除了不同的 tuple )和流(不是流类型!)单独强加装饰 . (即, std::vector<int> 可以为不同的流对象提供不同的装饰 . )

    A)默认装饰

    默认前缀是 "" (无),默认后缀是默认前缀,而默认前缀是 ", " (逗号空格) .

    B)通过专门化pretty :: defaulted类模板来定制类型的默认装饰

    struct defaulted 具有静态成员函数 decoration() ,返回 decor 对象,其中包含给定类型的默认值 .

    使用数组的示例:

    自定义默认阵列打印:

    namespace pretty
    {
      template<class T, std::size_t N>
      struct defaulted<T[N]>
      {
        static decor<T[N]> decoration()
        {
          return{ { "(" }, { ":" }, { ")" } };
        }
      };
    }
    

    打印arry数组:

    float e[5] = { 3.4f, 4.3f, 5.2f, 1.1f, 22.2f };
    std::cout << e << '\n'; // prints (3.4:4.3:5.2:1.1:22.2)
    

    为char流使用PRETTY_DEFAULT_DECORATION(TYPE,PREFIX,DELIM,POSTFIX,...)宏

    宏扩大到

    namespace pretty { 
      template< __VA_ARGS__ >
      struct defaulted< TYPE > {
        static decor< TYPE > decoration() {
          return { PREFIX, DELIM, POSTFIX };
        } 
      }; 
    }
    

    使上述部分专业化能够被重写

    PRETTY_DEFAULT_DECORATION(T[N], "", ";", "", class T, std::size_t N)
    

    或插入完整的专业化

    PRETTY_DEFAULT_DECORATION(std::vector<int>, "(", ", ", ")")
    

    包含了 wchar_t 流的另一个宏: PRETTY_DEFAULT_WDECORATION .

    C)在溪流上施加装饰

    函数 pretty::decoration 用于在特定流上强加装饰 . 有重载采取 - 一个字符串参数是分隔符(从默认类采用前缀和后缀) - 或三个字符串参数组装完整的装饰

    给定类型和流的完整装饰

    float e[3] = { 3.4f, 4.3f, 5.2f };
    std::stringstream u;
    // add { ; } decoration to u
    u << pretty::decoration<float[3]>("{", "; ", "}");
    
    // use { ; } decoration
    u << e << '\n'; // prints {3.4; 4.3; 5.2}
    
    // uses decoration returned by defaulted<float[3]>::decoration()
    std::cout << e; // prints 3.4, 4.3, 5.2
    

    为给定流定制分隔符

    PRETTY_DEFAULT_DECORATION(float[3], "{{{", ",", "}}}")
    
    std::stringstream v;
    v << e; // prints {{{3.4,4.3,5.2}}}
    
    v << pretty::decoration<float[3]>(":");
    v << e; // prints {{{3.4:4.3:5.2}}}
    
    v << pretty::decoration<float[3]>("((", "=", "))");
    v << e; // prints ((3.4=4.3=5.2))
    

    4. std :: tuple的特殊处理

    此代码不是允许对每个可能的元组类型进行特化,而是将 std::tuple<void*> 可用的任何装饰应用于所有类型的 std::tuple<...> .

    5.从流中删除自定义装饰

    要返回给定类型的默认装饰,请在流 s 上使用 pretty::clear 功能模板 .

    s << pretty::clear<std::vector<int>>();
    

    5.更多例子

    使用换行符分隔符打印“矩阵状”

    std::vector<std::vector<int>> m{ {1,2,3}, {4,5,6}, {7,8,9} };
    std::cout << pretty::decoration<std::vector<std::vector<int>>>("\n");
    std::cout << m;
    

    打印

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

    在ideone / KKUebZ上看到它

    6.代码

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

相关问题