漂亮的C ++ STL容器

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

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


我想有一个单一的模板,一劳永逸地照顾漂亮的打印所有STL容器通过operator<< 。 在伪代码中,我正在寻找这样的东西:

 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的东西。也许有什么特质,可以找出是否有必要的迭代器?

非常感谢!


更新(和解决scheme)

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

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

enable-iftypes特征需要C ++ 0x,但是有一些修改,应该可以创build一个C ++ 98版本。 元组需要可变参数模板,因此C ++ 0x。

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

标题(prettyprint.h):

 #ifndef H_PRETTY_PRINT #define H_PRETTY_PRINT #include <type_traits> #include <iostream> #include <utility> #include <tuple> namespace std { // Pre-declarations of container types so we don't actually have to include the relevant headers if not needed, speeding up compilation time. template<typename T, typename TTraits, typename TAllocator> class set; } namespace pretty_print { // SFINAE type trait to detect a container based on whether T::const_iterator exists. // (Improvement idea: check also if begin()/end() exist.) template<typename T> struct is_container_helper { private: template<typename C> static char test(typename C::const_iterator*); template<typename C> static int test(...); public: static const bool value = sizeof(test<T>(0)) == sizeof(char); }; // Basic is_container template; specialize to derive from std::true_type for all desired container types template<typename T> struct is_container : public ::std::integral_constant<bool, is_container_helper<T>::value> { }; // Holds the delimiter values for a specific character type template<typename TChar> struct delimiters_values { typedef TChar char_type; const TChar * prefix; const TChar * delimiter; const TChar * postfix; }; // Defines the delimiter values for a specific container and character type template<typename T, typename TChar> struct delimiters { typedef delimiters_values<TChar> type; static const type values; }; // Default delimiters template<typename T> struct delimiters<T, char> { static const delimiters_values<char> values; }; template<typename T> const delimiters_values<char> delimiters<T, char>::values = { "[", ", ", "]" }; template<typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; }; template<typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"[", L", ", L"]" }; // Delimiters for set template<typename T, typename TTraits, typename TAllocator> struct delimiters< ::std::set<T, TTraits, TAllocator>, char> { static const delimiters_values<char> values; }; template<typename T, typename TTraits, typename TAllocator> const delimiters_values<char> delimiters< ::std::set<T, TTraits, TAllocator>, char>::values = { "{", ", ", "}" }; template<typename T, typename TTraits, typename TAllocator> struct delimiters< ::std::set<T, TTraits, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; }; template<typename T, typename TTraits, typename TAllocator> const delimiters_values<wchar_t> delimiters< ::std::set<T, TTraits, TAllocator>, wchar_t>::values = { L"{", L", ", L"}" }; // Delimiters for pair (reused for tuple, see below) template<typename T1, typename T2> struct delimiters< ::std::pair<T1, T2>, char> { static const delimiters_values<char> values; }; template<typename T1, typename T2> const delimiters_values<char> delimiters< ::std::pair<T1, T2>, char>::values = { "(", ", ", ")" }; template<typename T1, typename T2> struct delimiters< ::std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; }; template<typename T1, typename T2> const delimiters_values<wchar_t> delimiters< ::std::pair<T1, T2>, wchar_t>::values = { L"(", L", ", L")" }; // Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type. template<typename T, typename TChar = char, typename TCharTraits = ::std::char_traits<TChar>, typename TDelimiters = delimiters<T, TChar>> struct print_container_helper { typedef TChar char_type; typedef TDelimiters delimiters_type; typedef std::basic_ostream<TChar, TCharTraits> & ostream_type; print_container_helper(const T & container) : _container(container) { } inline void operator()(ostream_type & stream) const { if (delimiters_type::values.prefix != NULL) stream << delimiters_type::values.prefix; for (typename T::const_iterator beg = _container.begin(), end = _container.end(), it = beg; it != end; ++it) { if (it != beg && delimiters_type::values.delimiter != NULL) stream << delimiters_type::values.delimiter; stream << *it; } if (delimiters_type::values.postfix != NULL) stream << delimiters_type::values.postfix; } private: const T & _container; }; // Type-erasing helper class for easy use of custom delimiters. // Requires TCharTraits = std::char_traits<TChar> and TChar = char or wchar_t, and MyDelims needs to be defined for TChar. // Usage: "cout << pretty_print::custom_delims<MyDelims>(x)". struct custom_delims_base { virtual ~custom_delims_base() { } virtual ::std::ostream & stream(::std::ostream &) = 0; virtual ::std::wostream & stream(::std::wostream &) = 0; }; template <typename T, typename Delims> struct custom_delims_wrapper : public custom_delims_base { custom_delims_wrapper(const T & t) : t(t) { } ::std::ostream & stream(::std::ostream & stream) { return stream << ::pretty_print::print_container_helper<T, char, ::std::char_traits<char>, Delims>(t); } ::std::wostream & stream(::std::wostream & stream) { return stream << ::pretty_print::print_container_helper<T, wchar_t, ::std::char_traits<wchar_t>, Delims>(t); } private: const T & t; }; template <typename Delims> struct custom_delims { template <typename Container> custom_delims(const Container & c) : base(new custom_delims_wrapper<Container, Delims>(c)) { } ~custom_delims() { delete base; } custom_delims_base * base; }; } // namespace pretty_print template <typename TChar, typename TCharTraits, typename Delims> inline std::basic_ostream<TChar, TCharTraits> & operator<<(std::basic_ostream<TChar, TCharTraits> & stream, const pretty_print::custom_delims<Delims> & p) { return p.base->stream(stream); } // Template aliases for char and wchar_t delimiters // Enable these if you have compiler support // // Implement as "template<T, C, A> const sdelims::type sdelims<std::set<T,C,A>>::values = { ... }." //template<typename T> using pp_sdelims = pretty_print::delimiters<T, char>; //template<typename T> using pp_wsdelims = pretty_print::delimiters<T, wchar_t>; namespace std { // Prints a print_container_helper to the specified stream. template<typename T, typename TChar, typename TCharTraits, typename TDelimiters> inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const ::pretty_print::print_container_helper<T, TChar, TCharTraits, TDelimiters> & helper) { helper(stream); return stream; } // Prints a container to the stream using default delimiters template<typename T, typename TChar, typename TCharTraits> inline typename enable_if< ::pretty_print::is_container<T>::value, basic_ostream<TChar, TCharTraits>&>::type operator<<(basic_ostream<TChar, TCharTraits> & stream, const T & container) { return stream << ::pretty_print::print_container_helper<T, TChar, TCharTraits>(container); } // Prints a pair to the stream using delimiters from delimiters<std::pair<T1, T2>>. template<typename T1, typename T2, typename TChar, typename TCharTraits> inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const pair<T1, T2> & value) { if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.prefix != NULL) stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.prefix; stream << value.first; if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.delimiter != NULL) stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.delimiter; stream << value.second; if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.postfix != NULL) stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.postfix; return stream; } } // namespace std // Prints a tuple to the stream using delimiters from delimiters<std::pair<tuple_dummy_t, tuple_dummy_t>>. namespace pretty_print { struct tuple_dummy_t { }; // Just if you want special delimiters for tuples. typedef std::pair<tuple_dummy_t, tuple_dummy_t> tuple_dummy_pair; template<typename Tuple, size_t N, typename TChar, typename TCharTraits> struct pretty_tuple_helper { static inline void print(::std::basic_ostream<TChar, TCharTraits> & stream, const Tuple & value) { pretty_tuple_helper<Tuple, N - 1, TChar, TCharTraits>::print(stream, value); if (delimiters<tuple_dummy_pair, TChar>::values.delimiter != NULL) stream << delimiters<tuple_dummy_pair, TChar>::values.delimiter; stream << std::get<N - 1>(value); } }; template<typename Tuple, typename TChar, typename TCharTraits> struct pretty_tuple_helper<Tuple, 1, TChar, TCharTraits> { static inline void print(::std::basic_ostream<TChar, TCharTraits> & stream, const Tuple & value) { stream << ::std::get<0>(value); } }; } // namespace pretty_print namespace std { template<typename TChar, typename TCharTraits, typename ...Args> inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const tuple<Args...> & value) { if (::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.prefix != NULL) stream << ::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.prefix; ::pretty_print::pretty_tuple_helper<const tuple<Args...> &, sizeof...(Args), TChar, TCharTraits>::print(stream, value); if (::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.postfix != NULL) stream << ::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.postfix; return stream; } } // namespace std // A wrapper for raw C-style arrays. Usage: int arr[] = { 1, 2, 4, 8, 16 }; std::cout << wrap_array(arr) << ... namespace pretty_print { template <typename T, size_t N> struct array_wrapper { typedef const T * const_iterator; typedef T value_type; array_wrapper(const T (& a)[N]) : _array(a) { } inline const_iterator begin() const { return _array; } inline const_iterator end() const { return _array + N; } private: const T * const _array; }; } // namespace pretty_print template <typename T, size_t N> inline pretty_print::array_wrapper<T, N> pretty_print_array(const T (& a)[N]) { return pretty_print::array_wrapper<T, N>(a); } #endif 

用法示例:

 #include <iostream> #include <vector> #include <unordered_map> #include <map> #include <set> #include <array> #include <tuple> #include <utility> #include <string> #include "prettyprint.h" // Specialization for a particular container template<> const pretty_print::delimiters_values<char> pretty_print::delimiters<std::vector<double>, char>::values = { "|| ", " : ", " ||" }; // Custom delimiters for one-off use struct MyDel { static const delimiters_values<char> values; }; const delimiters_values<char> MyDel::values = { "<", "; ", ">" }; int main(int argc, char * argv[]) { std::string cs; std::unordered_map<int, std::string> um; std::map<int, std::string> om; std::set<std::string> ss; std::vector<std::string> v; std::vector<std::vector<std::string>> vv; std::vector<std::pair<int, std::string>> vp; std::vector<double> vd; v.reserve(argc - 1); vv.reserve(argc - 1); vp.reserve(argc - 1); vd.reserve(argc - 1); std::cout << "Printing pairs." << std::endl; while (--argc) { std::string s(argv[argc]); std::pair<int, std::string> p(argc, s); um[argc] = s; om[argc] = s; v.push_back(s); vv.push_back(v); vp.push_back(p); vd.push_back(1./double(i)); ss.insert(s); cs += s; std::cout << " " << p << std::endl; } std::array<char, 5> a{{ 'h', 'e', 'l', 'l', 'o' }}; std::cout << "Vector: " << v << std::endl << "Incremental vector: " << vv << std::endl << "Another vector: " << vd << std::endl << "Pairs: " << vp << std::endl << "Set: " << ss << std::endl << "OMap: " << om << std::endl << "UMap: " << um << std::endl << "String: " << cs << std::endl << "Array: " << a << std::endl ; // Using custom delimiters manually: std::cout << pretty_print::print_container_helper<std::vector<std::string>, char, std::char_traits<char>, MyDel>(v) << std::endl; // Using custom delimiters with the type-erasing helper class std::cout << pretty_print::custom_delims<MyDel>(v) << std::endl; // Pairs and tuples and arrays: auto a1 = std::make_pair(std::string("Jello"), 9); auto a2 = std::make_tuple(1729); auto a3 = std::make_tuple("Qrgh", a1, 11); auto a4 = std::make_tuple(1729, 2875, std::pair<double, std::string>(1.5, "meow")); int arr[] = { 1, 4, 9, 16 }; std::cout << "C array: " << wrap_array(arr) << std::endl << "Pair: " << a1 << std::endl << "1-tuple: " << a2 << std::endl << "n-tuple: " << a3 << std::endl << "n-tuple: " << a4 << std::endl ; } 

进一步的改进想法:

  • 以同样的方式实现std::tuple<...>输出是我们为std::pair<S,T> 更新:现在这是一个单独的问题 ! Upupdate:现在已经实现了,谢谢Xeo!
  • 添加名称空间,以便助手类不会渗透到全局名称空间。 完成
  • 添加模板别名(或类似的东西),以方便制作自定义分隔符类,或者预处理器macros?

最近更新:

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

笔记:

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

谢谢大家谁贡献!


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

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

现在我们想要写std::cout << MyPrinter(v) << std::endl; 对于某些使用这些分隔符的容器 MyPrinter将是一个types擦除类,如下所示:

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

这个解决scheme受到了Marcelo的解决scheme的启发,有一些变化:

 #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_containertypes的特征,它必须专门用于所有需要支持的容器。 可能使用trait来检查value_typeconst_iteratorbegin() / end() ,但是我不确定我会推荐它,因为它可能匹配符合这些条件但实际上不是容器的东西,像std::basic_string 。 也像Marcelo的版本,它使用可以专门指定分隔符使用的模板。

主要区别在于我已经构build了一个pretty_ostream_iterator版本,它与std::ostream_iterator类似,但不会在最后一项之后输出分隔符。 格式化容器是由print_container_helper完成的,可以直接使用print_container_helper来打印没有is_container特征的容器,或者指定不同的分隔符types。

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

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

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

一旦您编写了一次性操作符<<重载,这应该自动地与任何集合一起工作,除了您将需要一个特殊的地图来打印该对,并且可能想要在那里定制分隔符。

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

您的“默认”打印机将只输出到stream。

好的,我们来做一个自定义打印机。 我将改变我的外部类到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; } 

Now by default it will work for maps as long as the key and value types are both printable and you can put in your own special item printer for when they are not (as you can with any other type), or if you do not want = as the delimiter.

I am moving the free-function to create these to the end now:

A free-function (iterator version) would look like something this and you could even have defaults:

 template<typename Collection> RangePrinter<typename Collection::const_iterator> rangePrinter ( const Collection& coll, const char * delim=",", const char * open="[", const char * close="]") { return RangePrinter< typename Collection::const_iterator > ( coll.begin(), coll.end(), delim, open, close ); } 

You could then use it for std::set by

  std::cout << outputFormatter( mySet ); 

You can also write free-function version that take a custom printer and ones that take two iterators. In any case they will resolve the template parameters for you, and you will be able to pass them through as temporaries.

Here is a working library, presented as a complete working program, that I just hacked together:

 #include <set> #include <vector> #include <iostream> #include <boost/utility/enable_if.hpp> // Default delimiters template <class C> struct Delims { static const char *delim[3]; }; template <class C> const char *Delims<C>::delim[3]={"[", ", ", "]"}; // Special delimiters for sets. template <typename T> struct Delims< std::set<T> > { static const char *delim[3]; }; template <typename T> const char *Delims< std::set<T> >::delim[3]={"{", ", ", "}"}; template <class C> struct IsContainer { enum { value = false }; }; template <typename T> struct IsContainer< std::vector<T> > { enum { value = true }; }; template <typename T> struct IsContainer< std::set<T> > { enum { value = true }; }; template <class C> typename boost::enable_if<IsContainer<C>, std::ostream&>::type operator<<(std::ostream & o, const C & x) { o << Delims<C>::delim[0]; for (typename C::const_iterator i = x.begin(); i != x.end(); ++i) { if (i != x.begin()) o << Delims<C>::delim[1]; o << *i; } o << Delims<C>::delim[2]; return o; } template <typename T> struct IsChar { enum { value = false }; }; template <> struct IsChar<char> { enum { value = true }; }; template <typename T, int N> typename boost::disable_if<IsChar<T>, std::ostream&>::type operator<<(std::ostream & o, const T (&x)[N]) { o << "["; for (int i = 0; i != N; ++i) { if (i) o << ","; o << x[i]; } o << "]"; return o; } int main() { std::vector<int> i; i.push_back(23); i.push_back(34); std::set<std::string> j; j.insert("hello"); j.insert("world"); double k[] = { 1.1, 2.2, M_PI, -1.0/123.0 }; std::cout << i << "\n" << j << "\n" << k << "\n"; } 

It currently only works with vector and set , but can be made to work with most containers, just by expanding on the IsContainer specializations. I haven't thought much about whether this code is minimal, but I can't immediately think of anything I could strip out as redundant.

EDIT: Just for kicks, I included a version that handles arrays. I had to exclude char arrays to avoid further ambiguities; it might still get into trouble with wchar_t[] .

I am going to add another answer here, because I have come up with a different approach to my previous one, and that is to use locale facets.

The basics are here

Essentially what you do is:

  1. Create a class that derives from std::locale::facet . The slight downside is that you will need a compilation unit somewhere to hold its id. Let's call it MyPrettyVectorPrinter. You'd probably give it a better name, and also create ones for pair and map.
  2. In your stream function, you check std::has_facet< MyPrettyVectorPrinter >
  3. If that returns true, extract it with std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. Your facet objects will have values for the delimiters and you can read them. If the facet isn't found, your print function ( operator<< ) provides default ones. Note you can do the same thing for reading a vector.

I like this method because you can use a default print whilst still being able to use a custom override.

The downsides are needing a library for your facet if used in multiple projects (so can't just be headers-only) and also the fact that you need to beware about the expense of creating a new locale object.

I have written this as a new solution rather than modify my other one because I believe both approaches can be correct and you take your pick.

The code proved to be handy on several occasions now and I feel the expense to get into customization as usage is quite low. Thus, I decided to release it under MIT license and provide a github repository where the header and a small example file can be downloaded.

http://djmuw.github.io/prettycc

0. Preface and wording

A 'decoration' in terms of this answer is a set of prefix-string, delimiter-string and a postfix-string. Where the prefix string is inserted into a stream before and the postfix string after the values of a container (see 2. Target containers). The delimiter string is inserted between the values of the respective container.

Note: Actually, this answer does not address the question to 100% since the decoration is not strictly compile time constant because runtime checks are required to check whether a custom decoration has been applied to the current stream. Nevertheless, I think it has some decent features.

Note2: May have minor bugs since it is not yet well tested.

1. General idea / usage

Zero additional code required for usage

It is to be kept as easy as

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

Easy customization …

… with respect to specific stream object

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

or with respect to all streams:

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

Rough description

  • The code includes a class template providing a default decoration for any type
  • which can be specialized to change the default decoration for (a) certain type(s) and it is
  • using the private storage provided by ios_base using xalloc / pword in order to save a pointer to a pretty::decor object specifically decorating a certain type on a certain stream.

If no pretty::decor<T> object for this stream has been set up explicitly pretty::defaulted<T, charT, chartraitT>::decoration() is called to obtain the default decoration for the given type. The class pretty::defaulted is to be specialized to customize default decorations.

2. Target objects / containers

Target objects obj for the 'pretty decoration' of this code are objects having either

  • overloads std::begin and std::end defined (includes C-Style arrays),
  • having begin(obj) and end(obj) available via ADL,
  • are of type std::tuple
  • or of type std::pair .

The code includes a trait for identification of classes with range features ( begin / end ). (There's no check included, whether begin(obj) == end(obj) is a valid expression, though.)

The code provides operator<< s in the global namespace that only apply for classes not having a more specialized version of operator<< available. Therefore, in example std::string is not printed using the operator in this code although having a valid begin / end pair.

3. Utilization and customization

Decorations can be imposed seperately for every type (except different tuple s) and stream (not stream type!). (Ie a std::vector<int> can have a different decorations for different stream objects.)

A) Default decoration

The default prefix is "" (nothing) as is the default postfix, while the default delimiter is ", " (comma+space).

B) Customized default decoration of a type by specializing the pretty::defaulted class template

The struct defaulted has a static member function decoration() returning a decor object which includes the default values for the given type.

Example using array:

Cutomize default array printing:

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

Print an arry array:

 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) 

Using the PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) macro for char streams

The macro expands to

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

enabling the above partial specialization to be rewritten to

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

or inserting a full specialization like

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

Another macro for wchar_t streams is included: PRETTY_DEFAULT_WDECORATION .

C) Impose decoration on streams

The function pretty::decoration is used to impose a decoration on a certain stream. There are overloads taking either – one string argument being the delimiter (adopting prefix and postfix from the defaulted class) – or three string arguments assembling the complete decoration

Complete decoration for given type and stream

 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 

Customization of delimiter for given stream

 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. Special handling of std::tuple

Instead of allowing a specialization for every possible tuple type, this code applies any decoration available for std::tuple<void*> to all kind of std::tuple<...> s.

5. Remove custom decoration from stream

To go back to the defaulted decoration for a given type use pretty::clear function template on the stream s .

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

5. Further examples

Printing "matrix-like" with newline delimiter

 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 

See it on ideone/KKUebZ

6. Code

 #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_ 

My solution is simple.h , which is part of scc package. All std containers, maps, sets, c-arrays are printable.

The goal here is to use ADL to do customization of how we pretty print.

You pass in a formatter tag, and override 4 functions (before, after, between and descend) in the tag's namespace. This changes how the formatter prints 'adornments' when iterating over containers.

A default formatter that does {(a->b),(c->d)} for maps, (a,b,c) for tupleoids, "hello" for strings, [x,y,z] for everything else included.

It should "just work" with 3rd party iterable types (and treat them like "everything else").

If you want custom adornments for your 3rd party iterables, simply create your own tag. It will take a bit of work to handle map descent (you need to overload pretty_print_descend( your_tag to return pretty_print::decorator::map_magic_tag<your_tag> ). Maybe there is a cleaner way to do this, not sure.

A little library to detect iterability, and tuple-ness:

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

A library that lets us visit the contents of an iterable or tuple type object:

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

A pretty printing library:

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

testing代码:

 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

This does use C++14 features (some _t aliases, and auto&& lambdas), but none are essential.