我如何打印出一个向量的内容?

如何将std::vector的内容打印到屏幕上?


实现以下operator<<的解决方案也会很好:

template<container C, class T, String delim = ", ", String open = "[", String close = "]">
std::ostream & operator<<(std::ostream & o, const C<T> & x)
{
// ... What can I write here?
}

以下是目前为止我所做的,没有单独的函数:

#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
#include <vector>
#include <sstream>
#include <cstdio>
using namespace std;


int main()
{
ifstream file("maze.txt");
if (file) {
vector<char> vec(istreambuf_iterator<char>(file), (istreambuf_iterator<char>()));
vector<char> path;
int x = 17;
char entrance = vec.at(16);
char firstsquare = vec.at(x);
if (entrance == 'S') {
path.push_back(entrance);
}
for (x = 17; isalpha(firstsquare); x++) {
path.push_back(firstsquare);
}
for (int i = 0; i < path.size(); i++) {
cout << path[i] << " ";
}
cout << endl;
return 0;
}
}
1082466 次浏览

这里是一个工作库,作为一个完整的工作程序,我刚刚把它组合在一起:

#include <set>
#include <vector>
#include <iostream>


#include <boost/utility/enable_if.hpp>


// Default delimiters
template <class C> struct Delims { static const char *delim[3]; };
template <class C> const char *Delims<C>::delim[3]={"[", ", ", "]"};
// Special delimiters for sets.
template <typename T> struct Delims< std::set<T> > { static const char *delim[3]; };
template <typename T> const char *Delims< std::set<T> >::delim[3]={"{", ", ", "}"};


template <class C> struct IsContainer { enum { value = false }; };
template <typename T> struct IsContainer< std::vector<T> > { enum { value = true }; };
template <typename T> struct IsContainer< std::set<T>    > { enum { value = true }; };


template <class C>
typename boost::enable_if<IsContainer<C>, std::ostream&>::type
operator<<(std::ostream & o, const C & x)
{
o << Delims<C>::delim[0];
for (typename C::const_iterator i = x.begin(); i != x.end(); ++i)
{
if (i != x.begin()) o << Delims<C>::delim[1];
o << *i;
}
o << Delims<C>::delim[2];
return o;
}


template <typename T> struct IsChar { enum { value = false }; };
template <> struct IsChar<char> { enum { value = true }; };


template <typename T, int N>
typename boost::disable_if<IsChar<T>, std::ostream&>::type
operator<<(std::ostream & o, const T (&x)[N])
{
o << "[";
for (int i = 0; i != N; ++i)
{
if (i) o << ",";
o << x[i];
}
o << "]";
return o;
}


int main()
{
std::vector<int> i;
i.push_back(23);
i.push_back(34);


std::set<std::string> j;
j.insert("hello");
j.insert("world");


double k[] = { 1.1, 2.2, M_PI, -1.0/123.0 };


std::cout << i << "\n" << j << "\n" << k << "\n";
}

它目前只适用于vectorset,但通过扩展IsContainer特殊化,可以使其适用于大多数容器。我没有过多地考虑这些代码是否最少,但我不能立即想到任何可以去掉的冗余代码。

编辑:只是为了好玩,我包含了一个处理数组的版本。我不得不排除字符数组,以避免进一步的歧义;它仍然可能在wchar_t[]中遇到麻烦。

经过多次编辑后,我们决定将包装集合的主类命名为RangePrinter

一旦你写了一次性的operator<<重载,它应该自动适用于任何集合,除非你需要一个特殊的映射来打印这个对,并且可能想在那里自定义分隔符。

你也可以有一个特别的“打印”;函数,而不是直接输出它,有点像STL算法允许您传入自定义谓词。对于map,您可以这样使用它,并为std::pair使用自定义打印机。

你的“default"打印机会把它输出到流中。

好的,让我们用一台定制打印机。我将外层类改为RangePrinter。所以我们有两个迭代器和一些分隔符,但还没有定制如何打印实际的项。

struct DefaultPrinter
{
template< typename T >
std::ostream & operator()( std::ostream& os, const T& t ) const
{
return os << t;
}


// overload for std::pair
template< typename K, typename V >
std::ostream & operator()( std::ostream & os, std::pair<K,V> const& p)
{
return os << p.first << '=' << p.second;
}
};


// some prototypes
template< typename FwdIter, typename Printer > class RangePrinter;


template< typename FwdIter, typename Printer >
std::ostream & operator<<( std::ostream &,
RangePrinter<FwdIter, Printer> const& );


template< typename FwdIter, typename Printer=DefaultPrinter >
class RangePrinter
{
FwdIter begin;
FwdIter end;
std::string delim;
std::string open;
std::string close;
Printer printer;


friend std::ostream& operator<< <>( std::ostream&,
RangePrinter<FwdIter,Printer> const& );


public:
RangePrinter( FwdIter b, FwdIter e, Printer p,
std::string const& d, std::string const & o, std::string const& c )
: begin( b ), end( e ), printer( p ), open( o ), close( c )
{
}


// with no "printer" variable
RangePrinter( FwdIter b, FwdIter e,
std::string const& d, std::string const & o, std::string const& c )
: begin( b ), end( e ), open( o ), close( c )
{
}


};




template<typename FwdIter, typename Printer>
std::ostream& operator<<( std::ostream& os,
RangePrinter<FwdIter, Printer> const& range )
{
const Printer & printer = range.printer;


os << range.open;
FwdIter begin = range.begin, end = range.end;


// print the first item
if (begin == end)
{
return os << range.close;
}


printer( os, *begin );


// print the rest with delim as a prefix
for( ++begin; begin != end; ++begin )
{
os << range.delim;
printer( os, *begin );
}
return os << range.close;
}

现在,默认情况下,只要键和值类型都是可打印的,它就可以用于映射,并且当它们都不是可打印的(就像任何其他类型一样),或者如果你不想要"="作为分隔符。

现在,我将自由函数移到末尾来创建这些:

free-function(迭代器版本)看起来像这样,你甚至可以有默认值:

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

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

 std::cout << outputFormatter( mySet );

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

这个解决方案的灵感来自Marcelo的解决方案,有一些变化:

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


// This works similar to ostream_iterator, but doesn't print a delimiter after the final item
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar> >
class pretty_ostream_iterator : public std::iterator<std::output_iterator_tag, void, void, void, void>
{
public:
typedef TChar char_type;
typedef TCharTraits traits_type;
typedef std::basic_ostream<TChar, TCharTraits> ostream_type;


pretty_ostream_iterator(ostream_type &stream, const char_type *delim = NULL)
: _stream(&stream), _delim(delim), _insertDelim(false)
{
}


pretty_ostream_iterator<T, TChar, TCharTraits>& operator=(const T &value)
{
if( _delim != NULL )
{
// Don't insert a delimiter if this is the first time the function is called
if( _insertDelim )
(*_stream) << _delim;
else
_insertDelim = true;
}
(*_stream) << value;
return *this;
}


pretty_ostream_iterator<T, TChar, TCharTraits>& operator*()
{
return *this;
}


pretty_ostream_iterator<T, TChar, TCharTraits>& operator++()
{
return *this;
}


pretty_ostream_iterator<T, TChar, TCharTraits>& operator++(int)
{
return *this;
}
private:
ostream_type *_stream;
const char_type *_delim;
bool _insertDelim;
};


#if _MSC_VER >= 1400


// Declare pretty_ostream_iterator as checked
template<typename T, typename TChar, typename TCharTraits>
struct std::_Is_checked_helper<pretty_ostream_iterator<T, TChar, TCharTraits> > : public std::tr1::true_type
{
};


#endif // _MSC_VER >= 1400


namespace std
{
// Pre-declarations of container types so we don't actually have to include the relevant headers if not needed, speeding up compilation time.
// These aren't necessary if you do actually include the headers.
template<typename T, typename TAllocator> class vector;
template<typename T, typename TAllocator> class list;
template<typename T, typename TTraits, typename TAllocator> class set;
template<typename TKey, typename TValue, typename TTraits, typename TAllocator> class map;
}


// Basic is_container template; specialize to derive from std::true_type for all desired container types
template<typename T> struct is_container : public std::false_type { };


// Mark vector as a container
template<typename T, typename TAllocator> struct is_container<std::vector<T, TAllocator> > : public std::true_type { };


// Mark list as a container
template<typename T, typename TAllocator> struct is_container<std::list<T, TAllocator> > : public std::true_type { };


// Mark set as a container
template<typename T, typename TTraits, typename TAllocator> struct is_container<std::set<T, TTraits, TAllocator> > : public std::true_type { };


// Mark map as a container
template<typename TKey, typename TValue, typename TTraits, typename TAllocator> struct is_container<std::map<TKey, TValue, TTraits, TAllocator> > : public std::true_type { };


// Holds the delimiter values for a specific character type
template<typename TChar>
struct delimiters_values
{
typedef TChar char_type;
const TChar *prefix;
const TChar *delimiter;
const TChar *postfix;
};


// Defines the delimiter values for a specific container and character type
template<typename T, typename TChar>
struct delimiters
{
static const delimiters_values<TChar> values;
};


// Default delimiters
template<typename T> struct delimiters<T, char> { static const delimiters_values<char> values; };
template<typename T> const delimiters_values<char> delimiters<T, char>::values = { "{ ", ", ", " }" };
template<typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"{ ", L", ", L" }" };


// Delimiters for set
template<typename T, typename TTraits, typename TAllocator> struct delimiters<std::set<T, TTraits, TAllocator>, char> { static const delimiters_values<char> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<char> delimiters<std::set<T, TTraits, TAllocator>, char>::values = { "[ ", ", ", " ]" };
template<typename T, typename TTraits, typename TAllocator> struct delimiters<std::set<T, TTraits, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<wchar_t> delimiters<std::set<T, TTraits, TAllocator>, wchar_t>::values = { L"[ ", L", ", L" ]" };


// Delimiters for pair
template<typename T1, typename T2> struct delimiters<std::pair<T1, T2>, char> { static const delimiters_values<char> values; };
template<typename T1, typename T2> const delimiters_values<char> delimiters<std::pair<T1, T2>, char>::values = { "(", ", ", ")" };
template<typename T1, typename T2> struct delimiters<std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T1, typename T2> const delimiters_values<wchar_t> delimiters<std::pair<T1, T2>, wchar_t>::values = { L"(", L", ", L")" };


// Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type.
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar>, typename TDelimiters = delimiters<T, TChar> >
struct print_container_helper
{
typedef TChar char_type;
typedef TDelimiters delimiters_type;
typedef std::basic_ostream<TChar, TCharTraits>& ostream_type;


print_container_helper(const T &container)
: _container(&container)
{
}


void operator()(ostream_type &stream) const
{
if( delimiters_type::values.prefix != NULL )
stream << delimiters_type::values.prefix;
std::copy(_container->begin(), _container->end(), pretty_ostream_iterator<typename T::value_type, TChar, TCharTraits>(stream, delimiters_type::values.delimiter));
if( delimiters_type::values.postfix != NULL )
stream << delimiters_type::values.postfix;
}
private:
const T *_container;
};


// Prints a print_container_helper to the specified stream.
template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const print_container_helper<T, TChar, TDelimiters> &helper)
{
helper(stream);
return stream;
}


// Prints a container to the stream using default delimiters
template<typename T, typename TChar, typename TCharTraits>
typename std::enable_if<is_container<T>::value, std::basic_ostream<TChar, TCharTraits>&>::type
operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const T &container)
{
stream << print_container_helper<T, TChar, TCharTraits>(container);
return stream;
}


// Prints a pair to the stream using delimiters from delimiters<std::pair<T1, T2>>.
template<typename T1, typename T2, typename TChar, typename TCharTraits>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const std::pair<T1, T2> &value)
{
if( delimiters<std::pair<T1, T2>, TChar>::values.prefix != NULL )
stream << delimiters<std::pair<T1, T2>, TChar>::values.prefix;


stream << value.first;


if( delimiters<std::pair<T1, T2>, TChar>::values.delimiter != NULL )
stream << delimiters<std::pair<T1, T2>, TChar>::values.delimiter;


stream << value.second;


if( delimiters<std::pair<T1, T2>, TChar>::values.postfix != NULL )
stream << delimiters<std::pair<T1, T2>, TChar>::values.postfix;
return stream;
}


// Used by the sample below to generate some values
struct fibonacci
{
fibonacci() : f1(0), f2(1) { }
int operator()()
{
int r = f1 + f2;
f1 = f2;
f2 = r;
return f1;
}
private:
int f1;
int f2;
};


int main()
{
std::vector<int> v;
std::generate_n(std::back_inserter(v), 10, fibonacci());


std::cout << v << std::endl;


// Example of using pretty_ostream_iterator directly
std::generate_n(pretty_ostream_iterator<int>(std::cout, ";"), 20, fibonacci());
std::cout << std::endl;
}

与Marcelo的版本一样,它使用了一个is_container类型的特征,必须为所有要支持的容器特殊化。也许可以使用trait来检查value_typeconst_iteratorbegin()/end(),但我不确定我是否会推荐这样做,因为它可能匹配符合这些标准但实际上不是容器的东西,比如std::basic_string。同样像Marcelo的版本一样,它使用可以专门化的模板来指定要使用的分隔符。

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

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

最后,我使用了std::enable_if,它是c++0x的一部分,在Visual c++ 2010和g++ 4.3(需要-std=c++0x标志)和更高版本中可用。这样就不依赖于Boost。

我的解决方案是simple.h,它是鳞状细胞癌包的一部分。所有std容器,map, set, c-arrays都是可打印的。

问题可能在前面的循环中:

(x = 17; isalpha(firstsquare); x++)

此循环将根本不运行(如果firstsquare是非字母)或将永远运行(如果它是字母)。原因是firstsquare不会随着x的增加而改变。

我看到了两个问题。正如在

for (x = 17; isalpha(firstsquare); x++)

if (entrance == 'S')中,如果入口字符与'S'不同,则没有任何东西被推送到路径向量,使其为空,因此在屏幕上不打印任何东西。你可以测试后者检查path.empty()或打印path.size()

不管怎样,使用字符串而不是向量不是更好吗?您可以像访问数组一样访问字符串内容,查找字符,提取子字符串并轻松打印字符串(无需循环)。

用字符串来完成这一切可能是用一种不那么复杂的方式来编写它,并且更容易发现问题。

如果你有一个c++ 11编译器,我建议使用一个基于范围的for循环(见下文);或者使用迭代器。但是你有几个选择,我将在下面解释。

基于范围的for循环(c++ 11)

在c++ 11(以及以后的版本)中,你可以使用新的基于范围的for循环,它看起来像这样:

std::vector<char> path;
// ...
for (char i: path)
std::cout << i << ' ';

for循环语句中的char类型应该是向量path的元素类型,而不是整数索引类型。换句话说,因为path的类型是std::vector<char>,所以应该出现在基于范围的for循环中的类型是char。然而,你可能经常会看到显式类型被auto占位符类型所取代:

for (auto i: path)
std::cout << i << ' ';

无论使用显式类型还是auto关键字,对象i具有一个值,该值是path对象中实际项的副本。因此,循环中对i的所有更改都不会保存在path本身中:

std::vector<char> path{'a', 'b', 'c'};


for (auto i: path) {
i = '_'; // 'i' is a copy of the element in 'path', so although
// we can change 'i' here perfectly fine, the elements
// of 'path' have not changed
std::cout << i << ' '; // will print: "_ _ _"
}


for (auto i: path) {
std::cout << i << ' '; // will print: "a b c"
}

如果你也想禁止在for循环中更改这个复制的i的值,你可以强制i的类型为const char,如下所示:

for (const auto i: path) {
i = '_'; // this will now produce a compiler error
std::cout << i << ' ';
}

如果你想修改path中的项,以便这些更改在for循环之外的path中持续存在,那么你可以像这样使用引用:

for (auto& i: path) {
i = '_'; // changes to 'i' will now also change the
// element in 'path' itself to that value
std::cout << i << ' ';
}

即使你不想修改path,如果对象的复制是昂贵的,你应该使用const引用,而不是按值复制:

for (const auto& i: path)
std::cout << i << ' ';

迭代器

在c++ 11之前,规范的解决方案是使用迭代器,这仍然是完全可以接受的。它们的使用方法如下:

std::vector<char> path;
// ...
for (std::vector<char>::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';

如果你想在for循环中修改向量的内容,那么使用iterator而不是const_iterator

补充:typedef / type alias (c++ 11) / auto (c++ 11)

这不是另一个解决方案,而是上述iterator解决方案的补充。如果你正在使用c++ 11标准(或更高版本),那么你可以使用auto关键字来提高可读性:

for (auto i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';

这里i的类型是非const(即,编译器将使用std::vector<char>::iterator作为i的类型)。这是因为我们调用了begin方法,所以编译器由此推导出i的类型。如果我们改为调用cbegin方法("c"for const),则i将是std::vector<char>::const_iterator:

for (auto i = path.cbegin(); i != path.cend(); ++i) {
*i = '_'; // will produce a compiler error
std::cout << *i << ' ';
}

如果你不习惯编译器推断类型,那么在c++ 11中,你可以使用类型别名来避免一直输入向量(养成一个好习惯):

using Path = std::vector<char>; // C++11 onwards only
Path path; // 'Path' is an alias for std::vector<char>
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';

如果你无法访问c++ 11编译器(或者出于某种原因不喜欢类型别名语法),那么你可以使用更传统的typedef:

typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
Path path;
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';

注:

在这一点上,您以前可能遇到过迭代器,也可能没有听说过迭代器是您“应该”遇到的东西。要用,可能会疑惑为什么。答案并不容易理解,但是,简而言之,迭代器是一种抽象,可以使您免受操作细节的影响。

有一个对象(迭代器)来执行你想要的操作(比如顺序访问)比你自己编写细节("detail "作为实际访问vector元素的代码)。你应该注意到,在for循环中,你只要求迭代器返回一个值(*i,其中i是迭代器)——你永远不会直接与path本身交互。逻辑是这样的:创建一个迭代器,并给它你想要循环的对象(iterator i = path.begin()),然后你所要做的就是让迭代器为你获取下一个值(*i);你永远不必担心迭代器到底是如何做到这一点的——那是它的事,与你无关。

好吧,但重点是什么?想象一下,如果获取一个值不简单。如果需要做点工作呢?你不需要担心,因为迭代器已经为你处理了——它会整理细节,你所需要做的就是向它请求一个值。此外,如果将容器从std::vector更改为其他容器呢?理论上,即使访问新容器中的元素的细节发生了变化,代码也不会改变:记住,迭代器在幕后为你整理了所有细节,所以你根本不需要改变代码——你只需要向迭代器请求容器中的下一个值,就像以前一样。

所以,虽然这看起来像是对vector循环的过度使用,但迭代器的概念背后有很好的理由,所以你最好习惯使用它们。

索引

你也可以使用整数类型显式地在for循环中为vector元素建立索引:

for (int i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';

如果要这样做,最好使用容器的成员类型,如果它们可用且合适的话。对于这个作业,std::vector有一个名为size_type的成员类型:它是由size方法返回的类型。

typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
for (Path::size_type i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';

为什么不优先使用iterator解决方案呢?对于简单的情况,你可以这样做,但是使用iterator带来了几个优点,我在上面简要地概述了这些优点。因此,我的建议是避免使用这种方法,除非你有充分的理由。

std::复制(C + + 11)

看到约书亚的回答。可以使用STL算法std::copy将向量内容复制到输出流中。我没有什么要补充的,只是说我不使用这种方法;但除了习惯之外,没有什么好的理由。

std::范围:复制(C + + 20)

为了完整起见,c++ 20引入了range,它可以作用于std::vector的整个范围,因此不需要beginend:

#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support


std::vector<char> path;
// ...
std::ranges::copy(path, std::ostream_iterator<char>(std::cout, " "));

除非你有一个最新的编译器(在GCC上显然是至少10.1版本),即使你可能有一些c++ 20的特性可用,你也可能不会有范围支持。

过载std::上ostream:: operator< & lt;

另见克里斯的回答如下。这更像是对其他答案的补充,因为您仍然需要在重载中实现上面的解决方案之一,但好处是代码更简洁。这是你如何使用上面的std::ranges::copy解决方案:

#include <iostream>
#include <vector>
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support


using Path = std::vector<char>; // type alias for std::vector<char>


std::ostream& operator<< (std::ostream& out, const Path& v) {
if ( !v.empty() ) {
out << '[';
std::ranges::copy(v, std::ostream_iterator<char>(out, ", "));
out << "\b\b]"; // use two ANSI backspace characters '\b' to overwrite final ", "
}
return out;
}


int main() {
Path path{'/', 'f', 'o', 'o'};


// will output: "path: [/, f, o, o]"
std::cout << "path: " << path << std::endl;


return 0;
}

现在你可以像基本类型一样将Path对象传递给输出流。使用上述任何其他解决方案也应该同样简单。

结论

这里提供的任何解决方案都可以工作。哪一个是“最好的”,这取决于你(以及上下文或你的编码标准)。任何比这更详细的问题可能最好留给另一个问题,在那里可以正确地评估利弊,但一如既往,用户偏好总是起作用的:所提出的解决方案在客观上都是错误的,但有些对每个程序员来说都更好。

齿顶高

这是我之前发布的一个解决方案的扩展。由于这篇文章一直受到关注,我决定扩展它,并参考这里发布的其他优秀解决方案,至少是那些我个人过去至少使用过一次的解决方案。然而,我想鼓励读者看看下面的答案,因为其中可能有我已经忘记或不知道的好建议。

一个更简单的方法是使用标准复制算法:

#include <iostream>
#include <algorithm> // for copy
#include <iterator> // for ostream_iterator
#include <vector>


int main() {
/* Set up vector to hold chars a-z */
std::vector<char> path;
for (int ch = 'a'; ch <= 'z'; ++ch)
path.push_back(ch);


/* Print path vector to console */
std::copy(path.begin(), path.end(), std::ostream_iterator<char>(std::cout, " "));


return 0;
}

ostream_iterator被称为迭代器适配器。它被模板化在打印到流的类型上(在本例中为char)。cout(又名控制台输出)是我们想要写入的流,而空格字符(" ")是我们想要打印在存储在vector中的每个元素之间的内容。

这个标准算法非常强大,其他算法也是如此。标准库提供的强大功能和灵活性使它如此出色。想象一下:你可以用一个行代码将一个向量打印到控制台。您不必处理分隔符的特殊情况。您不需要担心for循环。标准库为您完成了这一切。

我将在这里添加另一个答案,因为我已经提出了与之前的方法不同的方法,那就是使用locale facet。

基本的是在这里

基本上你要做的是:

  1. 创建一个派生自std::locale::facet的类。稍有缺点的是,您将需要一个编译单元来保存它的id。我们叫它MyPrettyVectorPrinter。你可能会给它一个更好的名字,并为pair和map创建一个。
  2. 在流函数中,检查std::has_facet< MyPrettyVectorPrinter >
  3. 如果返回true,则使用std::use_facet< MyPrettyVectorPrinter >( os.getloc() )提取它
  4. facet对象将具有分隔符的值,您可以读取它们。如果没有找到facet,则print函数(operator<<)提供默认的facet。注意,读取一个向量也可以做同样的事情。

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

缺点是如果在多个项目中使用facet,则需要一个面向facet的库(因此不能仅仅是头文件),而且需要注意创建一个新的locale对象的开销。

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

在c++ 11中,你现在可以使用基于范围的for循环:

for (auto const& c : path)
std::cout << c << ' ';

我认为最好的方法是通过在程序中添加这个函数来重载operator<<:

#include <vector>
using std::vector;
#include <iostream>
using std::ostream;


template<typename T>
ostream& operator<< (ostream& out, const vector<T>& v) {
out << "{";
size_t last = v.size() - 1;
for(size_t i = 0; i < v.size(); ++i) {
out << v[i];
if (i != last)
out << ", ";
}
out << "}";
return out;
}

然后你可以在任何可能的向量上使用<<操作符,假设它的元素也定义了ostream& operator<<:

vector<string>  s = {"first", "second", "third"};
vector<bool>    b = {true, false, true, false, false};
vector<int>     i = {1, 2, 3, 4};
cout << s << endl;
cout << b << endl;
cout << i << endl;

输出:

{first, second, third}
{1, 0, 1, 0, 0}
{1, 2, 3, 4}

for_each + lambda表达式怎么样:

#include <vector>
#include <algorithm>
// ...
std::vector<char> vec;
// ...
std::for_each(
vec.cbegin(),
vec.cend(),
[] (const char c) {std::cout << c << " ";}
);
// ...

当然,基于范围为是这个具体任务最优雅的解决方案,但它也提供了许多其他可能性。

解释

for_each算法接受输入范围可调用对象,在范围内的每个元素上调用此对象。输入范围由两个迭代器定义。可调用对象可以是一个函数,一个指向函数的指针,一个重载() operator的类的对象,或者在本例中是lambda表达式。此表达式的形参与vector中元素的类型匹配。

这个实现的美妙之处在于lambda表达式的强大功能——您可以使用这种方法做更多的事情,而不仅仅是打印向量。

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

传入一个格式化程序标记,并在标记的名称空间中覆盖4个函数(before、after、between和descent)。这改变了在迭代容器时格式化程序打印“装饰品”的方式。

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

它应该“只适用于”第三方可迭代类型(并将它们视为“所有其他类型”)。

如果你想为你的第三方可迭代对象定制装饰,只需创建你自己的标签。处理地图下降需要一些工作(你需要重载pretty_print_descend( your_tag来返回pretty_print::decorator::map_magic_tag<your_tag>)。也许有更干净的方法,不确定。

一个用于检测可迭代性和元组性的小库:

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


template<class T>struct is_visitable:std::integral_constant<bool, is_iterable<T>{}||is_tupleoid<T>{}> {};

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

template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_first(C&& c, F&& f) {
using std::begin; using std::end;
auto&& b = begin(c);
auto&& e = end(c);
if (b==e)
return;
std::forward<F>(f)(*b);
}
template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_all_but_first(C&& c, F&& f) {
using std::begin; using std::end;
auto it = begin(c);
auto&& e = end(c);
if (it==e)
return;
it = std::next(it);
for( ; it!=e; it = std::next(it) ) {
f(*it);
}
}


namespace details {
template<class Tup, class F>
void visit_first( std::index_sequence<>, Tup&&, F&& ) {}
template<size_t... Is, class Tup, class F>
void visit_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
std::forward<F>(f)( std::get<0>( std::forward<Tup>(tup) ) );
}
template<class Tup, class F>
void visit_all_but_first( std::index_sequence<>, Tup&&, F&& ) {}
template<size_t... Is,class Tup, class F>
void visit_all_but_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
int unused[] = {0,((void)(
f( std::get<Is>(std::forward<Tup>(tup)) )
),0)...};
(void)(unused);
}
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_first(Tup&& tup, F&& f) {
details::visit_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_all_but_first(Tup&& tup, F&& f) {
details::visit_all_but_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}

一个漂亮的印刷库:

namespace pretty_print {
namespace decorator {
struct default_tag {};
template<class Old>
struct map_magic_tag:Old {}; // magic for maps


// Maps get {}s. Write trait `is_associative` to generalize:
template<class CharT, class Traits, class...Xs >
void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
s << CharT('{');
}


template<class CharT, class Traits, class...Xs >
void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
s << CharT('}');
}


// tuples and pairs get ():
template<class CharT, class Traits, class Tup >
std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
s << CharT('(');
}


template<class CharT, class Traits, class Tup >
std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
s << CharT(')');
}


// strings with the same character type get ""s:
template<class CharT, class Traits, class...Xs >
void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
s << CharT('"');
}
template<class CharT, class Traits, class...Xs >
void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
s << CharT('"');
}
// and pack the characters together:
template<class CharT, class Traits, class...Xs >
void pretty_print_between( default_tag, std::basic_ostream<CharT, Traits>&, std::basic_string<CharT, Xs...> const& ) {}


// map magic. When iterating over the contents of a map, use the map_magic_tag:
template<class...Xs>
map_magic_tag<default_tag> pretty_print_descend( default_tag, std::map<Xs...> const& ) {
return {};
}
template<class old_tag, class C>
old_tag pretty_print_descend( map_magic_tag<old_tag>, C const& ) {
return {};
}


// When printing a pair immediately within a map, use -> as a separator:
template<class old_tag, class CharT, class Traits, class...Xs >
void pretty_print_between( map_magic_tag<old_tag>, std::basic_ostream<CharT, Traits>& s, std::pair<Xs...> const& ) {
s << CharT('-') << CharT('>');
}
}


// default behavior:
template<class CharT, class Traits, class Tag, class Container >
void pretty_print_before( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
s << CharT('[');
}
template<class CharT, class Traits, class Tag, class Container >
void pretty_print_after( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
s << CharT(']');
}
template<class CharT, class Traits, class Tag, class Container >
void pretty_print_between( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
s << CharT(',');
}
template<class Tag, class Container>
Tag&& pretty_print_descend( Tag&& tag, Container const& ) {
return std::forward<Tag>(tag);
}


// print things by default by using <<:
template<class Tag=decorator::default_tag, class Scalar, class CharT, class Traits>
std::enable_if_t<!is_visitable<Scalar>{}> print( std::basic_ostream<CharT, Traits>& os, Scalar&& scalar, Tag&&=Tag{} ) {
os << std::forward<Scalar>(scalar);
}
// for anything visitable (see above), use the pretty print algorithm:
template<class Tag=decorator::default_tag, class C, class CharT, class Traits>
std::enable_if_t<is_visitable<C>{}> print( std::basic_ostream<CharT, Traits>& os, C&& c, Tag&& tag=Tag{} ) {
pretty_print_before( std::forward<Tag>(tag), os, std::forward<C>(c) );
visit_first( c, [&](auto&& elem) {
print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
});
visit_all_but_first( c, [&](auto&& elem) {
pretty_print_between( std::forward<Tag>(tag), os, std::forward<C>(c) );
print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
});
pretty_print_after( std::forward<Tag>(tag), os, std::forward<C>(c) );
}
}

测试代码:

int main() {
std::vector<int> x = {1,2,3};


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


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


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

live example .

这确实使用了c++ 14特性(一些_t别名和auto&& lambdas),但没有一个是必要的。

在c++ 11中,基于范围的for循环可能是一个很好的解决方案:

vector<char> items = {'a','b','c'};
for (char n : items)
cout << n << ' ';

输出:

a b c

只需将容器复制到控制台。

std::vector<int> v{1,2,3,4};
std::copy(v.begin(),v.end(),std::ostream_iterator<int>(std::cout, " " ));

应输出:

1 2 3 4

这些代码在现在的一些场合被证明是方便的,我觉得进入定制的费用是相当低的使用率。因此,我决定在MIT许可下发布它,并提供一个GitHub存储库,在那里可以下载头文件和一个小示例文件。

< a href = " http://djmuw.github。io / prettycc noreferrer“rel = > http://djmuw.github.io/prettycc < / >

0. 前言和措辞

就这个答案而言,“装饰”是一个前缀字符串、分隔符字符串和后缀字符串的集合。 其中,前缀字符串插入到流之前,后缀字符串插入到容器的值之后(参见2。目标容器)。 分隔符字符串被插入到各自容器的值之间

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

注2:可能有小错误,因为它还没有经过很好的测试。

1. 大意/使用

使用时不需要额外的代码

这是很容易做到的

#include <vector>
#include "pretty.h"


int main()
{
std::cout << std::vector<int>{1,2,3,4,5}; // prints 1, 2, 3, 4, 5
return 0;
}

易于定制…

... 对于特定的流对象

#include <vector>
#include "pretty.h"


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

或者就所有流而言:

#include <vector>
#include "pretty.h"


// set decoration for std::vector<int> for all ostream objects
PRETTY_DEFAULT_DECORATION(std::vector<int>, "{", ", ", "}")


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

粗略的描述

  • 该代码包括一个类模板,为任何类型提供默认装饰
  • 可以专门改变(a)某些类型的默认装饰,它是
  • 使用ios_base提供的私有存储,使用xalloc/pword来保存指向pretty::decor对象的指针,特别是在某个流上修饰某个类型。

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

2. 目标对象/容器

此代码的“漂亮的装饰”的目标对象obj是具有这两者的对象

  • 重载std::beginstd::end定义的数组(包括c风格数组),
  • 通过ADL提供begin(obj)end(obj)
  • 类型为std::tuple
  • std::pair类型。
代码包含一个trait,用于标识具有范围特征的类(begin/end)。 (虽然没有包含检查,begin(obj) == end(obj)是否是一个有效的表达式)

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

3.利用和定制

装饰可以分别施加给每种类型(除了不同的tuples)和流(不是流类型!) (即std::vector<int>可以对不同的流对象有不同的装饰。)

A)默认装饰

默认前缀是""(无),默认后缀也是如此,而默认分隔符是", "(逗号+空格)。

B)通过特殊化pretty::defaulted类模板定制类型的默认装饰

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

使用数组的示例:

自定义默认数组打印:

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

打印一个数组:

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

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

宏展开为

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

使上面的部分专门化可以重写为

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

或者插入一个完整的专门化

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

包含另一个用于wchar_t流的宏:PRETTY_DEFAULT_WDECORATION

C)对溪流进行装饰

函数pretty::decoration用于对某个流施加修饰。 两者都有超载 -一个字符串参数作为分隔符(采用默认类的前缀和后缀) -或三个字符串参数组成完整的装饰

完整的装饰为给定类型和流

float e[3] = { 3.4f, 4.3f, 5.2f };
std::stringstream u;
// add { ; } decoration to u
u << pretty::decoration<float[3]>("{", "; ", "}");


// use { ; } decoration
u << e << '\n'; // prints {3.4; 4.3; 5.2}


// uses decoration returned by defaulted<float[3]>::decoration()
std::cout << e; // prints 3.4, 4.3, 5.2

自定义给定流的分隔符

PRETTY_DEFAULT_DECORATION(float[3], "\{\{{", ",", "}}}")


std::stringstream v;
v << e; // prints \{\{{3.4,4.3,5.2}}}


v << pretty::decoration<float[3]>(":");
v << e; // prints \{\{{3.4:4.3:5.2}}}


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

4. std::tuple的特殊处理

这段代码没有允许对每一种可能的元组类型进行特化,而是将std::tuple<void*>可用的任何修饰应用到所有类型的__abc1。

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

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

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

5. 进一步的例子

用换行分隔符打印“类似矩阵”

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

打印

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

ideone / KKUebZ上见

6. 代码

#ifndef pretty_print_0x57547_sa4884X_0_1_h_guard_
#define pretty_print_0x57547_sa4884X_0_1_h_guard_


#include <string>
#include <iostream>
#include <type_traits>
#include <iterator>
#include <utility>


#define PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) \
namespace pretty { template< __VA_ARGS__ >\
struct defaulted< TYPE > {\
static decor< TYPE > decoration(){\
return { PREFIX, DELIM, POSTFIX };\
} /*decoration*/ }; /*defaulted*/} /*pretty*/


#define PRETTY_DEFAULT_WDECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) \
namespace pretty { template< __VA_ARGS__ >\
struct defaulted< TYPE, wchar_t, std::char_traits<wchar_t> > {\
static decor< TYPE, wchar_t, std::char_traits<wchar_t> > decoration(){\
return { PREFIX, DELIM, POSTFIX };\
} /*decoration*/ }; /*defaulted*/} /*pretty*/


namespace pretty
{


namespace detail
{
// drag in begin and end overloads
using std::begin;
using std::end;
// helper template
template <int I> using _ol = std::integral_constant<int, I>*;
// SFINAE check whether T is a range with begin/end
template<class T>
class is_range
{
// helper function declarations using expression sfinae
template <class U, _ol<0> = nullptr>
static std::false_type b(...);
template <class U, _ol<1> = nullptr>
static auto b(U &v) -> decltype(begin(v), std::true_type());
template <class U, _ol<0> = nullptr>
static std::false_type e(...);
template <class U, _ol<1> = nullptr>
static auto e(U &v) -> decltype(end(v), std::true_type());
// return types
using b_return = decltype(b<T>(std::declval<T&>()));
using e_return = decltype(e<T>(std::declval<T&>()));
public:
static const bool value = b_return::value && e_return::value;
};
}


// holder class for data
template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
struct decor
{
static const int xindex;
std::basic_string<CharT, TraitT> prefix, delimiter, postfix;
decor(std::basic_string<CharT, TraitT> const & pre = "",
std::basic_string<CharT, TraitT> const & delim = "",
std::basic_string<CharT, TraitT> const & post = "")
: prefix(pre), delimiter(delim), postfix(post) {}
};


template<class T, class charT, class traits>
int const decor<T, charT, traits>::xindex = std::ios_base::xalloc();


namespace detail
{


template<class T, class CharT, class TraitT>
void manage_decor(std::ios_base::event evt, std::ios_base &s, int const idx)
{
using deco_type = decor<T, CharT, TraitT>;
if (evt == std::ios_base::erase_event)
{ // erase deco
void const * const p = s.pword(idx);
if (p)
{
delete static_cast<deco_type const * const>(p);
s.pword(idx) = nullptr;
}
}
else if (evt == std::ios_base::copyfmt_event)
{ // copy deco
void const * const p = s.pword(idx);
if (p)
{
auto np = new deco_type{ *static_cast<deco_type const * const>(p) };
s.pword(idx) = static_cast<void*>(np);
}
}
}


template<class T> struct clearer {};


template<class T, class CharT, class TraitT>
std::basic_ostream<CharT, TraitT>& operator<< (
std::basic_ostream<CharT, TraitT> &s, clearer<T> const &)
{
using deco_type = decor<T, CharT, TraitT>;
void const * const p = s.pword(deco_type::xindex);
if (p)
{ // delete if set
delete static_cast<deco_type const *>(p);
s.pword(deco_type::xindex) = nullptr;
}
return s;
}


template <class CharT>
struct default_data { static const CharT * decor[3]; };
template <>
const char * default_data<char>::decor[3] = { "", ", ", "" };
template <>
const wchar_t * default_data<wchar_t>::decor[3] = { L"", L", ", L"" };


}


// Clear decoration for T
template<class T>
detail::clearer<T> clear() { return{}; }
template<class T, class CharT, class TraitT>
void clear(std::basic_ostream<CharT, TraitT> &s) { s << detail::clearer<T>{}; }


// impose decoration on ostream
template<class T, class CharT, class TraitT>
std::basic_ostream<CharT, TraitT>& operator<<(
std::basic_ostream<CharT, TraitT> &s, decor<T, CharT, TraitT> && h)
{
using deco_type = decor<T, CharT, TraitT>;
void const * const p = s.pword(deco_type::xindex);
// delete if already set
if (p) delete static_cast<deco_type const *>(p);
s.pword(deco_type::xindex) = static_cast<void *>(new deco_type{ std::move(h) });
// check whether we alread have a callback registered
if (s.iword(deco_type::xindex) == 0)
{ // if this is not the case register callback and set iword
s.register_callback(detail::manage_decor<T, CharT, TraitT>, deco_type::xindex);
s.iword(deco_type::xindex) = 1;
}
return s;
}


template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
struct defaulted
{
static inline decor<T, CharT, TraitT> decoration()
{
return{ detail::default_data<CharT>::decor[0],
detail::default_data<CharT>::decor[1],
detail::default_data<CharT>::decor[2] };
}
};


template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
decor<T, CharT, TraitT> decoration(
std::basic_string<CharT, TraitT> const & prefix,
std::basic_string<CharT, TraitT> const & delimiter,
std::basic_string<CharT, TraitT> const & postfix)
{
return{ prefix, delimiter, postfix };
}


template<class T, class CharT = char,
class TraitT = std::char_traits < CharT >>
decor<T, CharT, TraitT> decoration(
std::basic_string<CharT, TraitT> const & delimiter)
{
using str_type = std::basic_string<CharT, TraitT>;
return{ defaulted<T, CharT, TraitT>::decoration().prefix,
delimiter, defaulted<T, CharT, TraitT>::decoration().postfix };
}


template<class T, class CharT = char,
class TraitT = std::char_traits < CharT >>
decor<T, CharT, TraitT> decoration(CharT const * const prefix,
CharT const * const delimiter, CharT const * const postfix)
{
using str_type = std::basic_string<CharT, TraitT>;
return{ str_type{ prefix }, str_type{ delimiter }, str_type{ postfix } };
}


template<class T, class CharT = char,
class TraitT = std::char_traits < CharT >>
decor<T, CharT, TraitT> decoration(CharT const * const delimiter)
{
using str_type = std::basic_string<CharT, TraitT>;
return{ defaulted<T, CharT, TraitT>::decoration().prefix,
str_type{ delimiter }, defaulted<T, CharT, TraitT>::decoration().postfix };
}


template<typename T, std::size_t N, std::size_t L>
struct tuple
{
template<class CharT, class TraitT>
static void print(std::basic_ostream<CharT, TraitT>& s, T const & value,
std::basic_string<CharT, TraitT> const &delimiter)
{
s << std::get<N>(value) << delimiter;
tuple<T, N + 1, L>::print(s, value, delimiter);
}
};


template<typename T, std::size_t N>
struct tuple<T, N, N>
{
template<class CharT, class TraitT>
static void print(std::basic_ostream<CharT, TraitT>& s, T const & value,
std::basic_string<CharT, TraitT> const &) {
s << std::get<N>(value);
}
};


}


template<class CharT, class TraitT>
std::basic_ostream<CharT, TraitT> & operator<< (
std::basic_ostream<CharT, TraitT> &s, std::tuple<> const & v)
{
using deco_type = pretty::decor<std::tuple<void*>, CharT, TraitT>;
using defaulted_type = pretty::defaulted<std::tuple<void*>, CharT, TraitT>;
void const * const p = s.pword(deco_type::xindex);
auto const d = static_cast<deco_type const * const>(p);
s << (d ? d->prefix : defaulted_type::decoration().prefix);
s << (d ? d->postfix : defaulted_type::decoration().postfix);
return s;
}


template<class CharT, class TraitT, class ... T>
std::basic_ostream<CharT, TraitT> & operator<< (
std::basic_ostream<CharT, TraitT> &s, std::tuple<T...> const & v)
{
using deco_type = pretty::decor<std::tuple<void*>, CharT, TraitT>;
using defaulted_type = pretty::defaulted<std::tuple<void*>, CharT, TraitT>;
using pretty_tuple = pretty::tuple<std::tuple<T...>, 0U, sizeof...(T)-1U>;
void const * const p = s.pword(deco_type::xindex);
auto const d = static_cast<deco_type const * const>(p);
s << (d ? d->prefix : defaulted_type::decoration().prefix);
pretty_tuple::print(s, v, d ? d->delimiter :
defaulted_type::decoration().delimiter);
s << (d ? d->postfix : defaulted_type::decoration().postfix);
return s;
}


template<class T, class U, class CharT, class TraitT>
std::basic_ostream<CharT, TraitT> & operator<< (
std::basic_ostream<CharT, TraitT> &s, std::pair<T, U> const & v)
{
using deco_type = pretty::decor<std::pair<T, U>, CharT, TraitT>;
using defaulted_type = pretty::defaulted<std::pair<T, U>, CharT, TraitT>;
void const * const p = s.pword(deco_type::xindex);
auto const d = static_cast<deco_type const * const>(p);
s << (d ? d->prefix : defaulted_type::decoration().prefix);
s << v.first;
s << (d ? d->delimiter : defaulted_type::decoration().delimiter);
s << v.second;
s << (d ? d->postfix : defaulted_type::decoration().postfix);
return s;
}




template<class T, class CharT = char,
class TraitT = std::char_traits < CharT >>
typename std::enable_if < pretty::detail::is_range<T>::value,
std::basic_ostream < CharT, TraitT >> ::type & operator<< (
std::basic_ostream<CharT, TraitT> &s, T const & v)
{
bool first(true);
using deco_type = pretty::decor<T, CharT, TraitT>;
using default_type = pretty::defaulted<T, CharT, TraitT>;
void const * const p = s.pword(deco_type::xindex);
auto d = static_cast<pretty::decor<T, CharT, TraitT> const * const>(p);
s << (d ? d->prefix : default_type::decoration().prefix);
for (auto const & e : v)
{ // v is range thus range based for works
if (!first) s << (d ? d->delimiter : default_type::decoration().delimiter);
s << e;
first = false;
}
s << (d ? d->postfix : default_type::decoration().postfix);
return s;
}


#endif // pretty_print_0x57547_sa4884X_0_1_h_guard_

过载operator< & lt;:

template<typename OutStream, typename T>
OutStream& operator<< (OutStream& out, const vector<T>& v)
{
for (auto const& tmp : v)
out << tmp << " ";
out << endl;
return out;
}

用法:

vector <int> test {1,2,3};
wcout << test; // or any output stream

这个答案是基于琐拉瓦的回答的,但我不能在那里留下评论。

你可以用cbegincend代替auto (c++ 11)/typedef版本const

for (auto i = path.cbegin(); i != path.cend(); ++i)
std::cout << *i << ' ';

使用std::copy,但没有额外的尾随分隔符

使用std::copy的替代/修改方法(如最初在@JoshuaKravtiz回答中使用的那样),但不在最后一个元素后面包含额外的尾随分隔符:

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


template <typename T>
void print_contents(const std::vector<T>& v, const char * const separator = " ")
{
if(!v.empty())
{
std::copy(v.begin(),
--v.end(),
std::ostream_iterator<T>(std::cout, separator));
std::cout << v.back() << "\n";
}
}


// example usage
int main() {
std::vector<int> v{1, 2, 3, 4};
print_contents(v);      // '1 2 3 4'
print_contents(v, ":"); // '1:2:3:4'
v = {};
print_contents(v);      // ... no std::cout
v = {1};
print_contents(v);      // '1'
return 0;
}

用于自定义POD类型容器的示例:

// includes and 'print_contents(...)' as above ...


class Foo
{
int i;
friend std::ostream& operator<<(std::ostream& out, const Foo& obj);
public:
Foo(const int i) : i(i) {}
};


std::ostream& operator<<(std::ostream& out, const Foo& obj)
{
return out << "foo_" << obj.i;
}


int main() {
std::vector<Foo> v{1, 2, 3, 4};
print_contents(v);      // 'foo_1 foo_2 foo_3 foo_4'
print_contents(v, ":"); // 'foo_1:foo_2:foo_3:foo_4'
v = {};
print_contents(v);      // ... no std::cout
v = {1};
print_contents(v);      // 'foo_1'
return 0;
}

在c++中11

for (auto i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';


for(int i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';

你可以使用{fmt}库打印容器以及范围和元组。例如:

#include <vector>
#include <fmt/ranges.h>


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

打印

[1, 2, 3]

stdout (godbolt)。

我不建议为标准容器重载operator<<,因为它可能会引入ODR违规。

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

从第一个BoostCon(现在叫CppCon)出来,我和另外两个人致力于一个库来实现这一点。主要的问题是需要扩展namespace std。结果证明,这对一个增强库来说是不可取的。

不幸的是,到代码的链接不再工作,但您可能会在讨论中发现一些有趣的花絮(至少那些没有讨论如何命名它!)

http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html

对于那些感兴趣的人:我写了一个通用的解决方案,它两全其美,更通用于任何类型的范围,并在非算术类型周围加上引号(适合于类似字符串的类型)。此外,这种方法不应该有任何ADL问题,也可以避免“意外”(因为它是根据具体情况明确添加的):

template <typename T>
inline constexpr bool is_string_type_v = std::is_convertible_v<const T&, std::string_view>;


template<class T>
struct range_out {
range_out(T& range) : r_(range) {
}
T& r_;
static_assert(!::is_string_type_v<T>, "strings and string-like types should use operator << directly");
};


template <typename T>
std::ostream& operator<< (std::ostream& out, range_out<T>& range) {
constexpr bool is_string_like = is_string_type_v<T::value_type>;
constexpr std::string_view sep{ is_string_like ? "', '" : ", " };


if (!range.r_.empty()) {
out << (is_string_like ? "['" : "[");
out << *range.r_.begin();
for (auto it = range.r_.begin() + 1; it != range.r_.end(); ++it) {
out << sep << *it;
}
out << (is_string_like ? "']" : "]");
}
else {
out << "[]";
}


return out;
}

现在它在任何范围都很容易使用:

std::cout << range_out{ my_vector };

类似字符串的检查留有改进的空间。 在我的解决方案中,我也有static_assert检查,以避免std::basic_string<>,但为了简单起见,我把它放在这里

对于想要没有循环的一行程序的人:

我不敢相信没有人知道这一点,但也许是因为更像c的方法。无论如何,在没有循环的情况下这样做是完全安全的,在一行程序假设中,std::vector<char>是空终止的:

std::vector<char> test { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\0' };
std::cout << test.data() << std::endl;

但为了安全起见,我会像@Zorawar建议的那样,将其包装在ostream操作符中:

template <typename T>std::ostream& operator<< (std::ostream& out, std::vector<T>& v)
{
v.push_back('\0'); // safety-check!
out << v.data();
return out;
}


std::cout << test << std::endl; // will print 'Hello, world!'

我们可以通过使用printf来实现类似的行为:

fprintf(stdout, "%s\n", &test[0]); // will also print 'Hello, world!'

注意:

重载的ostream操作符需要接受非const vector。这可能会使程序不安全或引入不可用的代码。此外,由于追加了空字符,可能会发生std::vector的重新分配。因此,使用带有迭代器的for循环可能会更快。

这是我在2016年完成的实现版本

所有内容都在一个头文件中,所以很容易使用 https://github.com/skident/eos/blob/master/include/eos/io/print.hpp < / p >
/*! \file       print.hpp
*  \brief      Useful functions for work with STL containers.
*
*  Now it supports generic print for STL containers like: [elem1, elem2, elem3]
*  Supported STL conrainers: vector, deque, list, set multiset, unordered_set,
*  map, multimap, unordered_map, array
*
*  \author     Skident
*  \date       02.09.2016
*  \copyright  Skident Inc.
*/


#pragma once


// check is the C++11 or greater available (special hack for MSVC)
#if (defined(_MSC_VER) && __cplusplus >= 199711L) || __cplusplus >= 201103L
#define MODERN_CPP_AVAILABLE 1
#endif




#include <iostream>
#include <sstream>
#include <vector>
#include <deque>
#include <set>
#include <list>
#include <map>
#include <cctype>


#ifdef MODERN_CPP_AVAILABLE
#include <array>
#include <unordered_set>
#include <unordered_map>
#include <forward_list>
#endif




#define dump(value) std::cout << (#value) << ": " << (value) << std::endl


#define BUILD_CONTENT                                                       \
std::stringstream ss;                                               \
for (; it != collection.end(); ++it)                                \
{                                                                   \
ss << *it << elem_separator;                                    \
}                                                                   \




#define BUILD_MAP_CONTENT                                                   \
std::stringstream ss;                                               \
for (; it != collection.end(); ++it)                                \
{                                                                   \
ss  << it->first                                                \
<< keyval_separator                                         \
<< it->second                                               \
<< elem_separator;                                          \
}                                                                   \




#define COMPILE_CONTENT                                                     \
std::string data = ss.str();                                        \
if (!data.empty() && !elem_separator.empty())                       \
data = data.substr(0, data.rfind(elem_separator));              \
std::string result = first_bracket + data + last_bracket;           \
os << result;                                                       \
if (needEndl)                                                       \
os << std::endl;                                                \






////
///
///
/// Template definitions
///
///


//generic template for classes: deque, list, forward_list, vector
#define VECTOR_AND_CO_TEMPLATE                                          \
template<                                                           \
template<class T,                                               \
class Alloc = std::allocator<T> >                      \
class Container, class Type, class Alloc>                       \


#define SET_TEMPLATE                                                    \
template<                                                           \
template<class T,                                               \
class Compare = std::less<T>,                          \
class Alloc = std::allocator<T> >                      \
class Container, class T, class Compare, class Alloc>       \


#define USET_TEMPLATE                                                   \
template<                                                           \
template < class Key,                                                   \
class Hash = std::hash<Key>,                                 \
class Pred = std::equal_to<Key>,                             \
class Alloc = std::allocator<Key>                            \
>                                                            \
class Container, class Key, class Hash, class Pred, class Alloc     \
>                                                                   \




#define MAP_TEMPLATE                                                    \
template<                                                           \
template<class Key,                                             \
class T,                                                \
class Compare = std::less<Key>,                         \
class Alloc = std::allocator<std::pair<const Key,T> >   \
>                                                       \
class Container, class Key,                                     \
class Value/*, class Compare, class Alloc*/>                    \




#define UMAP_TEMPLATE                                                   \
template<                                                           \
template<class Key,                                             \
class T,                                             \
class Hash = std::hash<Key>,                         \
class Pred = std::equal_to<Key>,                     \
class Alloc = std::allocator<std::pair<const Key,T> >\
>                                                      \
class Container, class Key, class Value,                        \
class Hash, class Pred, class Alloc                             \
>                                                       \




#define ARRAY_TEMPLATE                                                  \
template<                                                           \
template<class T, std::size_t N>                                \
class Array, class Type, std::size_t Size>                      \






namespace eos
{
static const std::string default_elem_separator     = ", ";
static const std::string default_keyval_separator   = " => ";
static const std::string default_first_bracket      = "[";
static const std::string default_last_bracket       = "]";




//! Prints template Container<T> as in Python
//! Supported containers: vector, deque, list, set, unordered_set(C++11), forward_list(C++11)
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
template<class Container>
void print( const Container& collection
, const std::string& elem_separator   = default_elem_separator
, const std::string& first_bracket    = default_first_bracket
, const std::string& last_bracket     = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}




//! Prints collections with one template argument and allocator as in Python.
//! Supported standard collections: vector, deque, list, forward_list
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
VECTOR_AND_CO_TEMPLATE
void print( const Container<Type>& collection
, const std::string& elem_separator   = default_elem_separator
, const std::string& first_bracket    = default_first_bracket
, const std::string& last_bracket     = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<Type>::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}




//! Prints collections like std:set<T, Compare, Alloc> as in Python
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
SET_TEMPLATE
void print( const Container<T, Compare, Alloc>& collection
, const std::string& elem_separator   = default_elem_separator
, const std::string& first_bracket    = default_first_bracket
, const std::string& last_bracket     = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<T, Compare, Alloc>::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}




//! Prints collections like std:unordered_set<Key, Hash, Pred, Alloc> as in Python
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
USET_TEMPLATE
void print( const Container<Key, Hash, Pred, Alloc>& collection
, const std::string& elem_separator   = default_elem_separator
, const std::string& first_bracket    = default_first_bracket
, const std::string& last_bracket     = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<Key, Hash, Pred, Alloc>::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}


//! Prints collections like std:map<T, U> as in Python
//! supports generic objects of std: map, multimap
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
MAP_TEMPLATE
void print(   const Container<Key, Value>& collection
, const std::string& elem_separator   = default_elem_separator
, const std::string& keyval_separator = default_keyval_separator
, const std::string& first_bracket    = default_first_bracket
, const std::string& last_bracket     = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<Key, Value>::const_iterator it = collection.begin();
BUILD_MAP_CONTENT
COMPILE_CONTENT
}


//! Prints classes like std:unordered_map as in Python
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
UMAP_TEMPLATE
void print(   const Container<Key, Value, Hash, Pred, Alloc>& collection
, const std::string& elem_separator   = default_elem_separator
, const std::string& keyval_separator = default_keyval_separator
, const std::string& first_bracket    = default_first_bracket
, const std::string& last_bracket     = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<Key, Value, Hash, Pred, Alloc>::const_iterator it = collection.begin();
BUILD_MAP_CONTENT
COMPILE_CONTENT
}


//! Prints collections like std:array<T, Size> as in Python
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
ARRAY_TEMPLATE
void print(   const Array<Type, Size>& collection
, const std::string& elem_separator   = default_elem_separator
, const std::string& first_bracket    = default_first_bracket
, const std::string& last_bracket     = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Array<Type, Size>::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}


//! Removes all whitespaces before data in string.
//! \param str string with data
//! \return string without whitespaces in left part
std::string ltrim(const std::string& str);


//! Removes all whitespaces after data in string
//! \param str string with data
//! \return string without whitespaces in right part
std::string rtrim(const std::string& str);


//! Removes all whitespaces before and after data in string
//! \param str string with data
//! \return string without whitespaces before and after data in string
std::string trim(const std::string& str);






////////////////////////////////////////////////////////////
////////////////////////ostream logic//////////////////////
/// Should be specified for concrete containers
/// because of another types can be suitable
/// for templates, for example templates break
/// the code like this "cout << string("hello") << endl;"
////////////////////////////////////////////////////////////






#define PROCESS_VALUE_COLLECTION(os, collection)                            \
print(  collection,                                                     \
default_elem_separator,                                         \
default_first_bracket,                                          \
default_last_bracket,                                           \
os,                                                             \
false                                                           \
);                                                                      \


#define PROCESS_KEY_VALUE_COLLECTION(os, collection)                        \
print(  collection,                                                     \
default_elem_separator,                                         \
default_keyval_separator,                                       \
default_first_bracket,                                          \
default_last_bracket,                                           \
os,                                                             \
false                                                           \
);                                                                      \


///< specialization for vector
template<class T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}


///< specialization for deque
template<class T>
std::ostream& operator<<(std::ostream& os, const std::deque<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}


///< specialization for list
template<class T>
std::ostream& operator<<(std::ostream& os, const std::list<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}


///< specialization for set
template<class T>
std::ostream& operator<<(std::ostream& os, const std::set<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}


///< specialization for multiset
template<class T>
std::ostream& operator<<(std::ostream& os, const std::multiset<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}


#ifdef MODERN_CPP_AVAILABLE
///< specialization for unordered_map
template<class T>
std::ostream& operator<<(std::ostream& os, const std::unordered_set<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}


///< specialization for forward_list
template<class T>
std::ostream& operator<<(std::ostream& os, const std::forward_list<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}


///< specialization for array
template<class T, std::size_t N>
std::ostream& operator<<(std::ostream& os, const std::array<T, N>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
#endif


///< specialization for map, multimap
MAP_TEMPLATE
std::ostream& operator<<(std::ostream& os, const Container<Key, Value>& collection)
{
PROCESS_KEY_VALUE_COLLECTION(os, collection)
return os;
}


///< specialization for unordered_map
UMAP_TEMPLATE
std::ostream& operator<<(std::ostream& os, const Container<Key, Value, Hash, Pred, Alloc>& collection)
{
PROCESS_KEY_VALUE_COLLECTION(os, collection)
return os;
}
}

模板收集:

应用std::cout <<std::to_string

std::vectorstd::arraystd::tuple

由于在cpp中打印一个向量被证明是惊人的工作量(至少与这个任务的基本程度相比),并且作为再次跨越相同问题的一个步骤,当使用其他容器时,这里有一个更通用的解决方案…

模板收集内容

这个模板集合处理3个容器类型: std::vectorstd::arraystd::tuple。 它为这些对象定义了std::to_string(),并可以通过std::cout << container;直接将它们打印出来

进一步定义了<<std::string << container的运算符。 这样就可以以紧凑的方式构造包含这些容器类型的字符串

std::string s1 = "s1: " + std::to_string(arr) + "; " + std::to_string(vec) + "; " + std::to_string(tup);

我们会讲到

std::string s2 = STR() << "s2: " << arr << "; " << vec << "; " << tup;

代码

你可以交互式地测试这段代码:在这里

#include <iostream>
#include <string>
#include <tuple>
#include <vector>
#include <array>


namespace std
{
// declations: needed for std::to_string(std::vector<std::tuple<int, float>>)
std::string to_string(std::string str);
std::string to_string(const char *str);
template<typename T, size_t N>
std::string to_string(std::array<T, N> const& arr);
template<typename T>
std::string to_string(std::vector<T> const& vec);
template<typename... Args>
std::string to_string(const std::tuple<Args...>& tup);
    

std::string to_string(std::string str)
{
return std::string(str);
}
std::string to_string(const char *str)
{
return std::string(str);
}


template<typename T, size_t N>
std::string to_string(std::array<T, N> const& arr)
{
std::string s="{";
for (std::size_t t = 0; t != N; ++t)
s += std::to_string(arr[t]) + (t+1 < N ? ", ":"");
return s + "}";
}


template<typename T>
std::string to_string(std::vector<T> const& vec)
{
std::string s="[";
for (std::size_t t = 0; t != vec.size(); ++t)
s += std::to_string(vec[t]) + (t+1 < vec.size() ? ", ":"");
return s + "]";
}
    

// to_string(tuple)
// https://en.cppreference.com/w/cpp/utility/tuple/operator%3D
template<class Tuple, std::size_t N>
struct TupleString
{
static std::string str(const Tuple& tup)
{
std::string out;
out += TupleString<Tuple, N-1>::str(tup);
out += ", ";
out += std::to_string(std::get<N-1>(tup));
return out;
}
};
template<class Tuple>
struct TupleString<Tuple, 1>
{
static std::string str(const Tuple& tup)
{
std::string out;
out += std::to_string(std::get<0>(tup));
return out;
}
};
template<typename... Args>
std::string to_string(const std::tuple<Args...>& tup)
{
std::string out = "(";
out += TupleString<decltype(tup), sizeof...(Args)>::str(tup);
out += ")";
return out;
}
} // namespace std




/**
* cout: cout << continer
*/
template <typename T, std::size_t N> // cout << array
std::ostream& operator <<(std::ostream &out, std::array<T, N> &con)
{
out <<  std::to_string(con);
return out;
}
template <typename T, typename A> // cout << vector
std::ostream& operator <<(std::ostream &out, std::vector<T, A> &con)
{
out <<  std::to_string(con);
return out;
}
template<typename... Args> // cout << tuple
std::ostream& operator <<(std::ostream &out, std::tuple<Args...> &con)
{
out <<  std::to_string(con);
return out;
}


/**
* Concatenate: string << continer
*/
template <class C>
std::string operator <<(std::string str, C &con)
{
std::string out = str;
out += std::to_string(con);
return out;
}
#define STR() std::string("")


int main()
{
std::array<int, 3> arr {1, 2, 3};
std::string sArr = std::to_string(arr);
std::cout << "std::array" << std::endl;
std::cout << "\ttest to_string: " << sArr << std::endl;
std::cout << "\ttest cout <<: " << arr << std::endl;
std::cout << "\ttest string <<: " << (std::string() << arr) << std::endl;
    

std::vector<std::string> vec {"a", "b"};
std::string sVec = std::to_string(vec);
std::cout << "std::vector" << std::endl;
std::cout << "\ttest to_string: " << sVec << std::endl;
std::cout << "\ttest cout <<: " << vec << std::endl;
std::cout << "\ttest string <<: " << (std::string() << vec) << std::endl;
    

std::tuple<int, std::string> tup = std::make_tuple(5, "five");
std::string sTup = std::to_string(tup);
std::cout << "std::tuple" << std::endl;
std::cout << "\ttest to_string: " << sTup << std::endl;
std::cout << "\ttest cout <<: " << tup << std::endl;
std::cout << "\ttest string <<: " << (std::string() << tup) << std::endl;
    

std::vector<std::tuple<int, float>> vt {std::make_tuple(1, .1), std::make_tuple(2, .2)};
std::string sVt = std::to_string(vt);
std::cout << "std::vector<std::tuple>" << std::endl;
std::cout << "\ttest to_string: " << sVt << std::endl;
std::cout << "\ttest cout <<: " << vt << std::endl;
std::cout << "\ttest string <<: " << (std::string() << vt) << std::endl;
    

std::cout << std::endl;
    

std::string s1 = "s1: " + std::to_string(arr) + "; " + std::to_string(vec) + "; " + std::to_string(tup);
std::cout << s1 << std::endl;
    

std::string s2 = STR() << "s2: " << arr << "; " << vec << "; " << tup;
std::cout << s2 << std::endl;


return 0;
}

输出

std::array
test to_string: {1, 2, 3}
test cout <<: {1, 2, 3}
test string <<: {1, 2, 3}
std::vector
test to_string: [a, b]
test cout <<: [a, b]
test string <<: [a, b]
std::tuple
test to_string: (5, five)
test cout <<: (5, five)
test string <<: (5, five)
std::vector<std::tuple>
test to_string: [(1, 0.100000), (2, 0.200000)]
test cout <<: [(1, 0.100000), (2, 0.200000)]
test string <<: [(1, 0.100000), (2, 0.200000)]


s1: {1, 2, 3}; [a, b]; (5, five)
s2: {1, 2, 3}; [a, b]; (5, five)

如果提高是一个选项,那么你可以使用boost::algorithm::join。例如,输出一个std::string的向量:

#include <boost/algorithm/string/join.hpp>


std::vector<std::string> vs { "some", "string", "vector" };
std::cout << boost::algorithm::join(vs, " | ") << '\n';

对于其他类型的向量,首先需要变换来string

#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>


#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/transformed.hpp>


int main()
{
using boost::adaptors::transformed;
using boost::algorithm::join;


// Generate the vector
std::vector<int> vi(10);
std::iota(vi.begin(), vi.end(), -3);


// Print out the vector
std::cout << join(vi |
transformed(static_cast<std::string(*)(int)>(std::to_string)),
", ")
<< '\n';
}

<一个href = " https://gcc.godbolt.org/ z: OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAM1QDsCBlZAQwBtMQBGAFlICsupVs1qhkAUgBMAISnTSAZ0ztkBPHUqZa6AMKpWAVwC2tENwDspLegAyeWpgByxgEaZiISwAdUCwuto9QxMzb19 / OjsHZyM3D0slFTU6BgJmYgIg41MLRWVMVQDU9IIop1d3T0U0jKyQ3IUa0vty2MrzAEpFVANiZA4AcikAZntkQywAanFhnTZgEkIEIxnscQAGAEERsYnMadn1RuJMZhXhta2d2nGDKZmdWmN3PAkLje3JUZu9g50ANwKRGIqw % 2 byk % 2 buzu % 2 bwellqvgiahp5oscmsecd7maexxupyahqilxeufxce / KF / WHwhHEETATBI9DMLzAhEEWm0BQ0YhGTDoAlEknbLb2AiTIzMewQDpg8yyLaTRWTAx bustkmneagzhmlkkbra9ki % 2LkkXnoGbyzZK5Wq4DquGa7WsBbEJZGLU4 % 2 bwwsekpwndbawgqegpuwrsb PAQTjrGXDS3W / 1 a9rpccrvfuybsrorvb46zs0itac0wzjlo % 2 biyiazqptfdwe00kkgaakcsjnufrjqkqoiakypkssb2 % 2 bxvj2sgeuzrue2hpvdtcw6szgzncs0qrrmntiad6leadwngneeaavb0ikk49gt9xdag96fgb0c8oi8ozwprb / FQ3ZkmMAhkHWhgNHK0lUrP0H0nacdEbYc22DYhO27F8 % 2 b1a4cikrgsg2by9yjfcn / gUJdpCbSQFFQXkP0o4jm0 / Zt8JDZslwAERwmCazQKc / lnXF51IpjJCXFcv34wDwNA8D41lTitgGLpWBAAZ % 2 bwguhtagdynnqvsejkorjmo3p bwanmbaboaaosrvn83gvigbgnk0ntsd0gynindzsgs7slnioby % 2 bibyzoa0ghvj0t9saaaxayz1jxyzuekth % 2BgRBayMLw8HYMgKAgNAcryypgBjSRSCoPKCHcA0IBcGyNJcex0gAT1UyzSGK3l6AAeVoVgOpS0gsAlUR2Ca0a8BOQpAQNEbMAADwKKdBmi0VlCm1g8BcWliDavQsCm9k8HdAZLK6Gh6CYNgOB4fhBGEUQUDkOQhF2g1YFoM4OA1AhSEBDwLvMdS7NQFkAgW4s % 2 bte4t / RmdiJEMmRJGixIgQCTRtDqUxOCsbQyhiOJBB8PxkkCfRslJ8IKaJioPHxjHChSJpccEZmKeKDJ6baRnqhKdmmaaXmSc4LoTL6e7lNU9TNKm2Klv8rziy87hJmAZBkEmGM8VEiBcEIDsRnxyY9BK LzPFs23pkKymq6CAkGK3L8vISgXdKjxxjpTgY3x6rWFq4h6sakaWp % 2 bg7oo0nqtaiaahqmsa6umkb8fmtr5qm5bvtqqbnrc6kdr29qjvwqzxxoy6qrorgwemh6beq56xft bqaft5eb / sBy % 2 bri6 % 2oQbBroIYp6HYZLBHhiRtu0fi / IWdMCBrCFgnbBaYnKnxsmIkp4I8dIbe6fXhmOfnrm2apkImbPooRePvmOYvvfH5KUXN4lnopa4GW1MihXVKVirNWkwfZqj9niFynBJgG3wMCK2RZzau3cOZSQHQbYo2kPbFKdlHL9kSmFCKpB3TcF8niXy5CKGUMofLEasV4ogESslWyaVMpOxAHWLwU53ZFRokg / mfIjb82unXO6XBeAAHdaReGjj / OWUVdKqRNpMcRSxJiANVurTW2tdaSCwcwthHCuGFU9vlEAlUBHAkEMI26DcJFSJkUIWWf9aGKK % 2 bkjfrqi1hkw0saluotoaqlxf1jhskugifofgdwhyceawiaqqhcske0oinqxqdckooy6i5lypcvl9nmp5bgwxzdmhwjwcwrthedggm4ljqk9ghkqbo5jci4ozowv0igfgndccaa % 3 d % 3 d % 3 d nofollow noreferrer“rel = >演示在Godbolt < / >

你可以使用std::experimental::make_ostream_joiner:

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>
#include <numeric>
#include <vector>
 

int main()
{
std::vector<int> vi(12);
std::iota(vi.begin(), vi.end(), -5);
std::cout << "Int vector:\n";
std::copy(std::begin(vi),
std::end(vi),
std::experimental::make_ostream_joiner(std::cout, ", "));


std::cout <<"\nString vector:\n[";
std::vector<std::string> vs { "some", "string", "vector" };
std::copy(std::begin(vs),
std::end(vs),
std::experimental::make_ostream_joiner(std::cout, "] - ["));
std::cout << "]\n";
}

<一个z: href = " https://godbolt.org/ OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAM1QDsCBlZAQwBtMQBGAFlJvoCqAZ0wAFAB4gA5AAYppAFZdSrZrVDIApACYAQjt2kR7ZATx1KmWugDCqVgFcAtrWVX0AGTy1MAOWcARpjEINq8AA6oQoTmtHaOLsqR0WZ0Xj7 % 2 btkehyuayjqm0datmxatxzq6ceqwxxaue6x6bwcpcjwuvidxtdu2z2vwaleaodstihfi6amzeyi5yansamzzswcsece6r2joyaikz84uyk2uy4uhbee5wjawa9itbzeteewfh2no0cw5lqsboz2srmmxdjn9kctv8zhcqqq3h8vrdfqdaedgbrnlctndvncauc1ga3tcmehow4ri7eahljzmbwqizegds bawzeywzlwee42nzm25tl5aqf5hkegladogsbwsmpxgte42aql % 2 bios15s3a6baiapvm % 2gBaACsCqVfP5BEFKDGjOBwJW2m0AEl6FLKR8QJpLTZaDptKtbSqHUK0OEAJ4QVUgXWs6Ujb52jMZxMm1OkdOZgvZ663e70NhC5kAa0wAH0ogRwZCawpUN5ggmo06HAQzaHex6hjaiTzI460N3Ea6PYHg8ViN5gL7RQGgyHLfpp4r87zEyLUWtE2CF5KyUIVlz3dohKh7n3L0f1HfQ3vqR7zwARcPb % 2 b1j1bxjuosmtcakiayjgwwadjmof5ubegjkkvw3popapbwzdvnwyiqk4zatj4xaadgzr9mglrvhakzrqgg5fnbibji6axuqggafqurfbrchkfkcugjkw0iwviriypiqdsdybggpyywtnivycpibdshig6kjwoqabxapwlp6tm3aajzckzwkybymwzco0jccjqnidi8hccamikcpykjhasawigtpooeedsgqfaqggavba0wccdffbbcixauraaqoaqatekusbseppdhahadytcsFlYmkFgzLqOwqX4OCph4BSLmlVclLdlMcjkPQ % 2 bspaweabmqmv2fgqunnc2x8xwdcmcwvu8bnggibiqvknuqjqcg0l6covuuzaix / oUjXmvl2jOfkfqxJY1hdFUpDuP0LQhNUyQxHQ11JFEL20PdWStD0Z11XQ9SdPYlRtP9hRA40bYDL9RgdOUIPdHDfTQw9wyjOMkzo4JUjCaQontRJUjiFpABs5qk9wSzAMgyBLDFulLBAuCECQ7ozNUSx2BFwXs5wQxcxtujKQ5XlIOFgXBeQlAS5FIQLGo0XaDIblUPFwRJSlpXpbQmVjblN4FUVJXteVitVaVNXnQ1qXNcgrWpQyXWlT1fUDRgbXKShY0jPwU1sBws1 % 2 b8iyiskvsjacoivrxobhbqeo2mvtsshcdp21byedug91r3ajp2paqz2fdnrcfyu32dh9gdfpdpfgodgpw5xso9mdcq3w3umzgj /我/ J2NCfZpVEyT5OU0sCvqPT2hajic9myzhx82a3os8e7pyolsd6clnkjagejycepqazabk43zpboca5 / 46lrpoa57mi6qoc0w7tqao79xcd7gyunh6e2kplbsx9gsocoesmefmqy0zpgztgtm15yzqhtudpayaidfqlxgcwrbuglicshvfee6dmethwxgsmbdj6ruxhkoec8idkn5rmeu1d %2BIHyPq0U0l95A3zvgTRyUgn5uQ8mpEYmkOScC1FpbgTjSbn0tO4zgRltAcmQTMVB5idHqRxidCRaCLEv33qQCkiULrcCAA" rel="nofollow noreferrer"> Godbolt Demo

我写了一个operator<<,它打印任何可迭代对象,包括自定义容器、标准容器和具有已知边界的数组。需要c++ 11:

template<typename Container, typename =
std::enable_if_t<std::is_same_v<std::void_t<
decltype(static_cast<typename Container::const_iterator (*)(const Container&)>(&std::cbegin)),
decltype(static_cast<typename Container::const_iterator (*)(const Container&)>(&std::cend))>, void>
&& !std::is_same_v<std::string, Container>>>
std::ostream& operator<<(std::ostream& out, const Container &vec)
{
std::cout << "[ ";
for(const auto& t: vec){
std::cout << t << " ";
}
std::cout << "] ";
return out;
}
# include <iostream>
# include <vector>


template <typename T>
std::ostream& operator<<( std::ostream& os, const std::vector<T>& v ){
for( int j = 0; j < v.size(); j++ ){
os << "[,"[ j > 0] << v[ j];
}
return os << "]";
}

你可以使用perr.h作为起点:

#include <vector>
#include "perr.h"


int main() {
std::vector< int > v = { 1, 2, 3 };
perr << v;
}

你只需要从GitHub (https://github.com/az5112/perr)抓取头。