漂亮打印C ++ STL容器 [英] Pretty-print C++ STL containers

查看:176
本文介绍了漂亮打印C ++ STL容器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请注意本文末尾的更新。



更新: =http://louisdx.github.com/cxx-prettyprint/>这个图书馆的GitHub上的公开项目






我想要一个单一的模板,通过运算符<< 来处理漂亮打印所有STL容器。 。在伪代码中,我正在寻找这样的:

  template< container C,class T,String delim = ,String open =[,String close =]> 
std :: ostream&运算符<<<(std :: ostream& o,const C< T& x)
{
o&打开;
// 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<分隔符
o<< *一世;
}
o<<关;
return o;
}

现在我已经看到了许多模板魔法,可能的,所以我想知道是否有人可以建议一些将匹配所有容器C.可能是一些trait-ish,可以弄清楚如果有一个必要的迭代器?



非常感谢!






更新(和解决方案)



频道9 上再次提出此问题后,我得到了一个梦幻般的答案从斯文Groot,结合一点SFINAE类型traiting,似乎解决了这个问题在一个完全一般和可嵌套的方式。分隔符可以单独指定,包括std :: set的示例专用化,以及使用自定义分隔符的示例。



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



enable-if类型trait需要C ++ 0x,但是有一些修改应该可以使C ++ 98版本这个。元组需要可变参数模板,因此C ++ 0x。



我要求Sven在这里发布解决方案,以便我可以接受它,但在此期间,我想发布代码自己参考。 (更新: Sven现在发布了他的代码,这是我接受的答案。我自己的代码使用容器类型traits,它为我工作,但可能会导致非容器类提供迭代器)



标题(prettyprint.h):



 code> #ifndef H_PRETTY_PRINT 
#define H_PRETTY_PRINT


#include< type_traits>
#include< iostream>
#include< utility>
#include< tuple>


命名空间std
{
//预先声明容器类型,所以如果不需要,我们实际上不必包含相关的头文件,加速编译时间。
template< typename T,typename TTraits,typename TAllocator>类集;
}

命名空间pretty_print
{

// SFINAE类型trait根据是否存在T :: const_iterator来检测容器。
//(改进想法:检查begin()/ end()是否存在)

template< typename T>
struct is_container_helper
{
private:
template< typename C> static char test(typename C :: const_iterator *);
template< typename C>静态int测试(...);
public:
static const bool value = sizeof(test< T>(0))== sizeof(char);
};


//基本is_container模板;专门从所有期望的容器类型的std :: true_type派生

template< typename T> struct is_container:public :: std :: integral_constant< bool,is_container_helper< T> :: value> {};


//保存特定字符类型的分隔符值

模板< typename TChar>
struct delimiters_values
{
typedef TChar char_type;
const TChar * prefix;
const TChar * delimiter;
const TChar * postfix;
};


//定义特定容器和字符类型的分隔符值

template< typename T,typename TChar>
struct delimiters
{
typedef delimiters_values< TChar>类型;
static const type values;
};


//默认分隔符

模板< typename T> struct delimiters< T,char> {static const delimiters_values< char>值; };
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>值; };
template< typename T> const delimiters_values< wchar_t>分隔符< T,wchar_t> :: values = {L[,L,,L]};


//集合的分隔符

模板< typename T,typename TTraits,typename TAllocator> struct delimiters& :: std :: set< T,TTraits,TAllocator>,char> {static const delimiters_values< char>值; };
template< typename T,typename TTraits,typename TAllocator> const delimiters_values< char>分隔符:: 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>值; };
template< typename T,typename TTraits,typename TAllocator> const delimiters_values< wchar_t>分隔符:: std :: set< T,TTraits,TAllocator>,wchar_t> :: values = {L{,L,,L}};


//对的分隔符(对于元组重复使用,见下文)

模板< typename T1,typename T2> struct delimiters& :: std :: pair< T1,T2>,char> {static const delimiters_values< char>值; };
template< typename T1,typename T2> const delimiters_values< char>分隔符:: 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>值; };
template< typename T1,typename T2> const delimiters_values< wchar_t>分隔符:: std :: pair< T1,T2>,wchar_t> :: values = {L(,L,,L)};


//打印容器的函子。如果要指定非默认分隔符类型,可以直接使用它。

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 & 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<< *它;
}

if(delimiters_type :: values.postfix!= NULL)
stream<< delimiters_type :: values.postfix;
}

private:
const T& _容器;
};


//类型删除助手类,以方便使用自定义分隔符。
//需要TCharTraits = std :: char_traits< TChar>和TChar = char或wchar_t,MyDelims需要为TChar定义。
//用法: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 }
custom_delims_base * base;
};

} //命名空间pretty_print


模板< typename TChar,typename TCharTraits,typename Delims>
inline std :: basic_ostream< TChar,TCharTraits> & <<<<<<(std :: basic_ostream< TChar,TCharTraits& stream,const pretty_print :: custom_delims< Delims>& p)
{
return p.base-& ;
}


// char和wchar_t分隔符的模板别名
//如果您有编译器支持,请启用这些别名
//
/ / Implement astemplate< T,C,A> const sdelims :: type sdelims< std :: set< T,C,A> :: values = {...}

// template< typename T>使用pp_sdelims = pretty_print :: delimiters< T,char> ;;
// template< typename T>使用pp_wsdelims = pretty_print :: delimiters< T,wchar_t> ;;


命名空间std
{
//打印一个print_container_helper到指定的流。

template< typename T,typename TChar,typename TCharTraits,typename TDelimiters>
inline basic_ostream< TChar,TCharTraits> &运算符<<<<<<<(basic_stream< TChar,TCharTraits>& stream,
const :: pretty_print :: print_container_helper< T,TChar,TCharTraits,TDelimiters& helper)
{
helper流);
return stream;
}

//使用默认分隔符打印一个容器

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 )
{
返回流<< :: pretty_print :: print_container_helper< T,TChar,TCharTraits>(container);
}

//使用分隔符< std :: pair< T1,T2>>打印一个使用分隔符的流。
template< typename T1,typename T2,typename TChar,typename TCharTraits>
inline basic_ostream< TChar,TCharTraits> &运算符<<<<<<<<(basic_stream< 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

//使用分隔符< std :: pair< tuple_dummy_t,tuple_dummy_t>>打印一个元组到流。

namespace pretty_print
{
struct tuple_dummy_t {}; //只是想要元组的特殊分隔符。

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<分隔符< 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> &运算符<<<<<<<(basic_ostream< TChar,TCharTraits>& stream,const tuple< Args ...& value)
{
if(:: pretty_print :: delimiters& :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;
}
} //命名空间std


//原始C风格数组的包装器。用法: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&
}


#endif

>使用示例:

  #include< iostream> 
#include< vector>
#include< unordered_map>
#include< map>
#include< set>
#include< array>
#include< tuple>
#include< utility>
#include< string>

#includeprettyprint.h

//特定容器的专用化
template<> const pretty_print :: delimiters_values< char> pretty_print :: delimiters< std :: vector< double>,char> :: values = {||,:,|| };

//一次性使用的自定义分隔符
struct MyDel {static const delimiters_values< char>值; };
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> m
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<< 打印对。 << 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
<< 另一个向量:< vd<< std :: endl
<< 对:< vp<< std :: endl
<< Set:<< ss << std :: endl
<< OMap:< om < std :: endl
<< UMap:< um < std :: endl
<< String:< cs < std :: endl
<< Array:< a<< std :: endl
;

//手动使用自定义分隔符:
std :: cout<< pretty_print :: print_container_helper< std :: vector< std :: string>,char,std :: char_traits< char>,MyDel>(v)< std :: endl;

//使用自定义分隔符和类型删除助手类
std :: cout<< pretty_print :: custom_delims< MyDel>(v)<< std :: endl;

//对和元组和数组:
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> 更新:现在是 SO的独立问题 Upupdate:现在已经实现了,感谢Xeo!

  • 添加命名空间,使得帮助类不会泄漏到全局命名空间。 / s>

  • 添加模板别名(或类似内容)以方便创建自定义分隔符类或预处理宏。



最近更新:




  • 自定义输出迭代器,在打印函数中使用一个简单的for循环。

  • 所有实现细节现在都在 pretty_print 命名空间中。

  • 修复了命名空间,使得只包含全局流运算符和 pretty_print_array



注意:




  • 移除输出迭代器意味着没有办法使用 std :: copy()来获得漂亮打印。

  • 这是一个有意识的设计决策,使分隔符编译时常量而不是对象常量。这意味着你不能在运行时动态提供分隔符,但它也意味着没有不必要的开销。基于对象的分隔符配置由Dennis Zickefoose在下面对Sven的代码的评论中提出。

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

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



<

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

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

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

  struct wrapper_base 
{
virtual〜wrapper_base(){}
virtual std :: ostream& stream(std :: ostream& o)= 0;
};

template< typename T,typename Delims>
struct wrapper:public wrapper_base
{
wrapper(const T& t):t(t){}
std :: ostream& stream(std :: ostream& o)
{
return o<< pretty_print :: print_container_helper< T,char,std :: char_traits< char>,Delims>(t);
}
private:
const T& t;
};

template< typename Delims>
struct MyPrinter
{
template< typename Container> MyPrinter(const Container& c):base(new wrapper< Container,Delims>(c)){}
〜MyPrinter }
wrapper_base * base;
};

template< typename Delims>
std :: ostream& operator<<<(std :: ostream& o,const MyPrinter< Delims && p){return p.base-> stream(o); }这个解决方案的灵感来自于Marcelo的解决方案,其中包含了一个用于解决方案的解决方案。很少更改:

  #include< iostream> 
#include< iterator>
#include< type_traits>
#include< vector>
#include< algorithm>

//这类似于ostream_iterator,但不会在最后一个项目之后打印分隔符
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)
{
//如果这是第一次使用,不要插入分隔符被称为
if(_insertDelim)
(* _stream)<< _delim;
else
_insertDelim = true;
}
(* _stream)<<值;
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

//将pretty_ostream_iterator声明为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

命名空间std
{
//预先声明容器类型,如果不需要,必须包括相关的头文件,从而加快编译时间。
//如果你真的包含头部,这些都不是必需的。
template< typename T,typename TAllocator>类向量;
template< typename T,typename TAllocator>类列表;
template< typename T,typename TTraits,typename TAllocator>类集;
template< typename TKey,typename TValue,typename TTraits,typename TAllocator>类映射;
}

//基本is_container模板;专门从所有期望的容器类型的std :: true_type派生
template< typename T> struct is_container:public std :: false_type {};

//将向量标记为容器
template< typename T,typename TAllocator> struct is_container< std :: vector< T,TAllocator> > :public std :: true_type {};

//将列表标记为容器
template< typename T,typename TAllocator> struct is_container< std :: list< T,TAllocator> > :public std :: true_type {};

//标记为容器
template< typename T,typename TTraits,typename TAllocator> struct is_container< std :: set< T,TTraits,TAllocator> > :public std :: true_type {};

//将地图标记为容器
模板< typename TKey,typename TValue,typename TTraits,typename TAllocator> struct is_container< std :: map< TKey,TValue,TTraits,TAllocator> > :public std :: true_type {};

//保存特定字符类型的分隔符值
template< typename TChar>
struct delimiters_values
{
typedef TChar char_type;
const TChar * prefix;
const TChar * delimiter;
const TChar * postfix;
};

//定义特定容器和字符类型的分隔符值
template< typename T,typename TChar>
struct delimiters
{
static const delimiters_values< TChar>值;
};

//默认分隔符
template< typename T> struct delimiters< T,char> {static const delimiters_values< char>值; };
template< typename T> const delimiters_values< char>分隔符< T,char> :: values = {{,,,}};
template< typename T> struct delimiters< T,wchar_t> {static const delimiters_values< wchar_t>值; };
template< typename T> const delimiters_values< wchar_t>分隔符< T,wchar_t> :: values = {L{,L,,L}};

//集合的分隔符
模板< typename T,typename TTraits,typename TAllocator> struct delimiters< std :: set< T,TTraits,TAllocator>,char> {static const delimiters_values< char>值; };
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>值; };
template< typename T,typename TTraits,typename TAllocator> const delimiters_values< wchar_t>分隔符< std :: set< T,TTraits,TAllocator>,wchar_t> :: values = {L[,L,,L]};

//对的分隔符
模板< typename T1,typename T2> struct delimiters< std :: pair< T1,T2>,char> {static const delimiters_values< char>值; };
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>值; };
template< typename T1,typename T2> const delimiters_values< wchar_t> delimiters< std :: pair< T1,T2>,wchar_t> :: values = {L(,L,,L)};

//打印容器的函数。如果要指定非默认分隔符类型,可以直接使用它。
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;
};

//将print_container_helper打印到指定的流。
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
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;
}

Like Marcelo’s version, it uses an is_container type trait that must be specialized for all containers that are to be supported. It may be possible to use a trait to check for value_type, const_iterator, begin()/end(), but I’m not sure I’d recommend that since it might match things that match those criteria but aren’t actually containers, like std::basic_string. Also like Marcelo’s version, it uses templates that can be specialized to specify the delimiters to use.



The major difference is that I’ve built my version around a pretty_ostream_iterator, which works similar to the std::ostream_iterator but doesn’t print a delimiter after the last item. Formatting the containers is done by the print_container_helper, which can be used directly to print containers without an is_container trait, or to specify a different delimiters type.



I’ve also defined is_container and delimiters so it will work for containers with non-standard predicates or allocators, and for both char and wchar_t. The operator<< function itself is also defined to work with both char and wchar_t streams.



Finally, I’ve used std::enable_if, which is available as part of C++0x, and works in Visual C++ 2010 and g++ 4.3 (needs the -std=c++0x flag) and later. This way there is no dependency on Boost.


Please take note of the updates at the end of this post.

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


I would like to have a single template that once and for all takes care of pretty-printing all STL containers via operator<<. In pseudo code, I'm looking for something like this:

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

Now I've seen plenty of template magic here on SO that I never thought possible, so I'm wondering if anyone can suggest something that would match all containers C. Maybe something trait-ish that can figure out if something has the necessary iterator?

Many thanks!


Update (and solution)

After raising this problem again on Channel 9, I got a fantastic answer from Sven Groot, which, combined with a bit of SFINAE type traiting, appears to solve the problem in a completely general and nestable fashion. The delimiters may be individually specialised, an example specialization for std::set is included, as well as an example of using custom delimiters.

The helper "wrap_array()" can be used to print raw C arrays. Update: Pairs and tuples are available for printing; default delimiters are round brackets.

The enable-if type trait requires C++0x, but with some modifications it should be possible to make a C++98 version of this. Tuples require variadic templates, hence C++0x.

I have asked Sven to post the solution here so that I can accept it, but in the meantime I'd like to post the code myself for reference. (Update: Sven has now posted his code below, which I made the accepted answer. My own code uses container type traits, which work for me but may cause unexpected behaviour with non-container classes that provide iterators.)

Header (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

Usage example:

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

Further ideas for improvements:

  • Implement output for std::tuple<...> in the same way is we have it for std::pair<S,T>. Update: This is now a separate question on SO! Upupdate: This has now been implemented, thanks to Xeo!
  • Add namespaces so that the helper classes don't bleed into the global namespace. Done
  • Add template aliases (or something similar) to facilitate making custom delimiter classes, or maybe preprocessor macros?

Recent updates:

  • I removed the custom output iterator in favour of a simple for loop in the print function.
  • All implementation details are now in the pretty_print namespace. Only the global stream operators and the pretty_print_array wrapper are in the global namespace.
  • Fixed the namespacing so that operator<< is now correctly in std.

Notes:

  • Removing the output iterator means that there is no way to use std::copy() to get pretty-printing. I might reinstate the pretty iterator if this is a desired feature, but Sven's code below has the implementation.
  • It was a conscious design decision to make the delimiters compile-time constants rather than object constants. That means that you cannot supply delimiters dynamically at runtime, but it also means that there's no unneeded overhead. An object-based delimiter configuration has been proposed by Dennis Zickefoose in a comment to Sven's code below. If desired, this could be implemented as an alternative feature.
  • It is currently not obvious how to customize nested container delimiters.
  • Bear in mind that the purpose of this library is to allow quick container printing facilities that require zero coding on your part. It is not an all-purpose formatting library, but rather a developing tool to alleviate the need to write boiler-plate code for container inspection.

Thank you to everyone who contributed!


Note: If you are looking for a quick way to deploy custom delimiters, here is one way using type erasure. We assume that you have already constructed a delimiter class, say MyDel, like so:

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

Now we want to be able to write std::cout << MyPrinter(v) << std::endl; for some container v using those delimiters. MyPrinter will be a type-erasing class, like so:

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

解决方案

This solution was inspired by Marcelo's solution, with a few changes:

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

Like Marcelo's version, it uses an is_container type trait that must be specialized for all containers that are to be supported. It may be possible to use a trait to check for value_type, const_iterator, begin()/end(), but I'm not sure I'd recommend that since it might match things that match those criteria but aren't actually containers, like std::basic_string. Also like Marcelo's version, it uses templates that can be specialized to specify the delimiters to use.

The major difference is that I've built my version around a pretty_ostream_iterator, which works similar to the std::ostream_iterator but doesn't print a delimiter after the last item. Formatting the containers is done by the print_container_helper, which can be used directly to print containers without an is_container trait, or to specify a different delimiters type.

I've also defined is_container and delimiters so it will work for containers with non-standard predicates or allocators, and for both char and wchar_t. The operator<< function itself is also defined to work with both char and wchar_t streams.

Finally, I've used std::enable_if, which is available as part of C++0x, and works in Visual C++ 2010 and g++ 4.3 (needs the -std=c++0x flag) and later. This way there is no dependency on Boost.

这篇关于漂亮打印C ++ STL容器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆