关于模板:Pretty-printC++ STL容器

Pretty-print C++ STL containers

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

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

我想有一个单一的模板,一次和所有负责漂亮的打印通过operator<<所有STL容器。在伪代码中,我在寻找类似这样的东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

现在我已经在这里看到了大量的模板魔术,所以我从来没有想过可能,所以我想知道是否有人可以建议一些与所有容器C匹配的东西。也许是一些特征性的东西,可以弄清楚某个东西是否有必要的迭代器?

多谢!

更新(和解决方案)

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

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

启用IF类型的特性需要C++0X,但是通过一些修改,应该可以制作一个C++ 98版本。元组需要可变的模板,因此C++0X。

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

标题(预打印.h):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
#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

使用实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <iostream>
#include <vector>
#include <unordered_map>
#include <map>
#include <set>
#include
#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
  ;
}

改进的进一步想法:

  • implement output for std::tuple<...>in the same way is we have it for std::pair.update:this is now a separate question on so!升级:这已经实现了,多亏了Xeo!
  • 添加命名空间,这样帮助器类就不会流血到全局命名空间中。完成
  • 添加模板别名(或类似的东西)以便于创建自定义分隔符类,或者可能是预处理器宏?

最近更新:

  • 为了在print函数中使用简单的for循环,我删除了自定义输出迭代器。
  • 所有实现细节现在都在pretty_print名称空间中。只有全局流操作符和pretty_print_array包装器位于全局命名空间中。
  • 修正了名称间距,使operator<<现在正确地位于std中。

笔记:

  • 删除输出迭代器意味着没有办法使用std::copy()获得漂亮的打印效果。如果这是一个需要的特性,我可能会恢复漂亮的迭代器,但是下面的sven代码有实现。
  • 让定界符编译时间常量而不是对象常量是一个有意识的设计决策。这意味着您不能在运行时动态地提供分隔符,但也意味着没有不必要的开销。Dennis Zickefoose在下面对Sven代码的注释中提出了一个基于对象的分隔符配置。如果需要的话,这可以作为一个替代特性来实现。
  • 目前还不清楚如何自定义嵌套容器分隔符。
  • 请记住,此库的目的是允许快速容器打印设备,要求您零编码。它不是一个通用的格式化库,而是一个开发工具,以减轻编写用于容器检查的锅炉板代码的需要。

感谢大家的贡献!

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

1
2
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将是一个类型擦除类,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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的解决方案,并进行了一些更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#include <iostream>
#include <iterator>
#include <type_traits>
#include <vector>
#include

// 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[cc lang="cpp"] > : 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_容器类型特性,该特性必须专门用于要支持的所有容器。可能可以使用一个特征来检查value_typeconst_iteratorbegin()end(),但我不确定我是否建议使用它,因为它可能匹配那些标准,但实际上不是容器,如std::basic_string。和Marcelo的版本一样,它使用可以专门指定要使用的分隔符的模板。

主要的区别在于,我构建的版本是围绕一个pretty_ostream_iterator,它的工作原理类似于std::ostream_iterator,但没有在最后一项后面打印分隔符。格式化容器是由print_container_helper完成的,它可以直接用于打印没有is_容器特性的容器,或者指定不同的分隔符类型。

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

最后,我使用了EDCOX1 12,它作为C++ 0x的一部分可用,并在Visual C++ 2010和G++ 4.3中工作(需要-STD= C++0X标志)和以后。这样就不依赖于提振。


这已经被编辑了几次,我们决定调用包装集合范围打印机的主类

一旦编写了一次性运算符<

你也可以有一个特殊的"打印"功能来使用这个项目,而不是直接输出它。有点像STL算法,允许您传入自定义谓词。使用map,您可以这样使用它,使用std::pair的自定义打印机。

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

好,让我们用一台定制打印机。我要把我的外班改成RangePrinter。所以我们有两个迭代器和一些定界符,但是还没有定制如何打印实际的项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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;
}

现在,默认情况下,只要键和值类型都是可打印的,并且您可以将它们放置在自己的特殊项打印机中,以便在它们不可用时(与任何其他类型相同),或者如果不希望=作为分隔符,则可以使用它。

我正在移动free函数来创建这些函数,直到现在:

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

1
2
3
4
5
6
7
8
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

1
 std::cout << outputFormatter( mySet );

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


这里是一个工作库,作为一个完整的工作程序呈现,我只是黑客一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#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 <<"
"
<< j <<"
"
<< k <<"
"
;
}

它目前只与vectorset一起工作,但是可以通过扩展IsContainer的专门化来与大多数容器一起工作。我没有考虑过这段代码是否是最小的,但是我不能马上想到任何可以去掉的冗余代码。

编辑:只是为了好玩,我包括了一个处理数组的版本。我不得不排除char数组以避免进一步的模糊性;它可能仍然会与wchar_t[]发生问题。


事实证明,该代码在很多情况下都很方便,而且由于使用率很低,我觉得进行定制的成本很低。因此,我决定在MIT许可下发布它,并提供一个Github存储库,在这里可以下载头文件和一个小的示例文件。好的。http://djmuw.github.io/prettyc0。前言和措词

这个答案中的"修饰"是一组前缀字符串、分隔符字符串和后缀字符串。其中前缀字符串在容器值之前插入流,后缀字符串在容器值之后插入流(请参见2)。目标容器)。分隔符字符串插入到相应容器的值之间。好的。

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

注2:可能有一些小错误,因为它还没有经过很好的测试。好的。1。总体思路/用途使用无需额外代码

它要像好的。

1
2
3
4
5
6
7
8
#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;
}

轻松自定义…

…关于特定流对象好的。

1
2
3
4
5
6
7
8
9
10
#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;
}

或关于所有溪流:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
#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对象,则调用pretty::defaulted::decoration()以获取给定类型的默认修饰。类pretty::defaulted专门用于自定义默认装饰。好的。2。目标对象/容器

用于"漂亮装饰"此代码的目标对象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。好的。三。利用和定制

每种类型(不同的tuples)和stream(非stream类型)都可以单独进行装饰。.(即,std::vector可以对不同的流对象进行不同的装饰。)好的。a)默认装修

默认前缀是""(无),默认后缀是","(逗号+空格)。好的。b)通过专门化pretty::defaulted类模板定制一个类型的默认装饰

struct defaulted有一个静态成员函数decoration()返回decor对象,该对象包含给定类型的默认值。好的。使用数组的示例:

自定义默认数组打印:好的。

1
2
3
4
5
6
7
8
9
10
11
namespace pretty
{
  template<class T, std::size_t N>
  struct defaulted<T[N]>
  {
    static decor<T[N]> decoration()
    {
      return{ {"(" }, {":" }, {")" } };
    }
  };
}

打印阵列:好的。

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

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

宏扩展到好的。

1
2
3
4
5
6
7
8
namespace pretty {
  template< __VA_ARGS__ >
  struct defaulted< TYPE > {
    static decor< TYPE > decoration() {
      return { PREFIX, DELIM, POSTFIX };
    }
  };
}

允许将上述部分专门化重写为好的。

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

或者插入一个完全专业化,比如好的。

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

另外一个用于wchar_t流的宏包括:PRETTY_DEFAULT_WDECORATION。好的。c)对河流进行装饰

函数pretty::decoration用于对某条流进行装饰。也有超载-一个字符串参数作为分隔符(采用来自默认类的前缀和后缀)-或三个字符串参数组合完整的装饰好的。给定类型和流的完整装饰

1
2
3
4
5
6
7
8
9
10
11
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 << '
'
; // 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

为给定流自定义分隔符

1
2
3
4
5
6
7
8
9
10
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可用的任何修饰应用到所有类型的std::tuple<...>中。好的。5。从流中移除自定义装饰

要返回给定类型的默认装饰,请使用流s上的pretty::clear函数模板。好的。

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

5。其他示例

用换行符打印"类似矩阵"好的。

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

印刷品好的。

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

在ideone/kkuebz上查看6。代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#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[cc lang="cpp"] 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_

好啊。


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

基本要素在这里

基本上你要做的是:

  • 创建从std::locale::facet派生的类。稍有缺点的是,您需要在某个地方有一个编译单元来保存它的ID。让我们称之为MyPrettyVectorPrinter。你可能会给它起个更好的名字,也会为配对和映射创建一个名字。
  • 在流函数中,检查std::has_facet< MyPrettyVectorPrinter >
  • 如果返回真值,则用std::use_facet< MyPrettyVectorPrinter >( os.getloc() )提取。
  • 方面对象将具有分隔符的值,您可以读取这些值。如果找不到方面,则打印功能(operator<<提供默认的方面。注意,你可以做同样的事情来读取一个向量。
  • 我喜欢这种方法,因为您可以使用默认打印,同时仍然可以使用自定义覆盖。

    如果在多个项目中使用,缺点是需要为您的方面提供一个库(因此不能只是头文件),而且需要注意创建一个新的区域设置对象的开销。

    我写这篇文章是作为一个新的解决方案,而不是修改我的另一个,因为我相信这两种方法都是正确的,你自己挑吧。


    您可以使用fmt库格式化容器以及范围和元组。例如:

    1
    2
    3
    4
    5
    6
    7
    #include <vector>
    #include <fmt/ranges.h>

    int main() {
      auto v = std::vector<int>{1, 2, 3};
      fmt::print("{}", v);
    }

    印刷品

    1
    {1, 2, 3}

    stdout

    免责声明:我是fmt的作者。


    这里的目标是使用ADL来定制我们漂亮的打印方式。

    传入格式化程序标记,并重写标记命名空间中的4个函数(before、after、between和descend)。这将更改格式化程序在遍历容器时打印"修饰"的方式。

    默认格式化程序,它为maps执行{(a->b),(c->d)},为tupleoid执行(a,b,c),为字符串执行"hello",为包括的所有内容执行[x,y,z]

    它应该"只适用于"第三方不可访问的类型(并像对待其他类型一样对待它们)。

    如果您想要为第三方iTerables定制装饰,只需创建自己的标签。处理地图下降需要一些工作(您需要超载pretty_print_descend( your_tag返回pretty_print::decorator::map_magic_tag)。也许有一个更清洁的方法来做这个,不确定。

    一个用于检测iterability和tuple ness的小库:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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>{}> {};

    允许我们访问iterable或tuple类型对象内容的库:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    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) );
    }

    一个漂亮的打印库:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    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, 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, 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) );
      }
    }

    测试代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    int main() {
      std::vector<int> x = {1,2,3};

      pretty_print::print( std::cout, x );
      std::cout <<"
    "
    ;

      std::map< std::string, int > m;
      m["hello"] = 3;
      m["world"] = 42;

      pretty_print::print( std::cout, m );
      std::cout <<"
    "
    ;
    }

    活生生的例子

    这确实使用C++ 14个特性(一些EDCOX1,10个别名,EDCOX1,11个)lambDas,但没有一个是必需的。


    我的解决方案是simple.h,它是SCC包的一部分。所有标准容器、地图、集合、C数组都可以打印。