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


实现以下操作符<<的解决方案也很好:

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

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

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

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

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


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

一旦您编写了一次性操作符<< overload,这将自动适用于任何集合,除非您将需要一个特殊的映射来打印对,并且可能希望在那里自定义分隔符。

您还可以在项目上使用特殊的“print”函数,而不是直接输出它,有点像STL算法允许您传入自定义谓词。对于map,您可以这样使用它,使用std::pair的自定义打印机。

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

好的,让我们用一台定制打印机。我将外层类改为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_type、const_iterator、begin()/end(),但我不确定我是否会推荐这样做,因为它可能匹配那些符合这些标准但实际上不是容器的东西,比如std::basic_string。同样像Marcelo的版本一样,它使用可以专门化的模板来指定要使用的分隔符。

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

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

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


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


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

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

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


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

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

要么是一个无限循环,要么根本没有执行,同样在if(入口== '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-loop语句中的type char应该是vector路径元素的类型,而不是整数索引类型。换句话说,因为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循环中修改vector的内容,那么使用iterator而不是const_iterator。

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

这不是另一个解决方案,而是对上述迭代器解决方案的补充。如果你正在使用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”表示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 << ' ';

注:

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

It is convenient to have an object (the iterator) that does the operation you want (like sequential access) rather than you writing the details yourself (the "details" being the code that does the actual accessing of the elements of the vector). You should notice that in the for-loop you are only ever asking the iterator to return you a value (*i, where i is the iterator) -- you are never interacting with path directly itself. The logic goes like this: you create an iterator and give it the object you want to loop over (iterator i = path.begin()), and then all you do is ask the iterator to get the next value for you (*i); you never had to worry exactly how the iterator did that -- that's its business, not yours.

OK, but what's the point? Well, imagine if getting a value wasn't simple. What if it involves a bit of work? You don't need to worry, because the iterator has handled that for you -- it sorts out the details, all you need to do is ask it for a value. Additionally, what if you change the container from std::vector to something else? In theory, your code doesn't change even if the details of how accessing elements in the new container does: remember, the iterator sorts all the details out for you behind the scenes, so you don't need to change your code at all -- you just ask the iterator for the next value in the container, same as before.

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

索引

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

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

如果要这样做,最好使用容器的成员类型,如果它们可用且合适的话。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] << ' ';

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

std::复制(C + + 11)

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

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

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

#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::操作符< <

下面是克里斯的回答。这更像是对其他答案的补充,因为您仍然需要在重载中实现上面的解决方案之一,但好处是代码更简洁。这是你如何使用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。

基本原理在这里

基本上你要做的是:

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

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

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

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


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

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

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

#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&运算符<<:

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是最优雅的解决方案,但它也提供了许多其他可能性。

解释

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

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


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

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

一个默认的格式化程序,它对map执行{(A ->b),(c->d)},对tupleoid执行(A,b,c),对字符串执行"hello",对包含的所有其他内容执行[x,y,z]。

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

如果你想为你的第三方可迭代对象定制装饰,只需创建你自己的标签。处理映射下降需要一些工作(您需要重载pretty_print_descent (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";
}

生活的例子

这确实使用了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存储库,在那里可以下载头文件和一个小示例文件。

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:: defaults <T, charT, chartraitT>::decoration()被调用以获取给定类型的默认装饰。 类pretty:: defaults专门用于定制默认装饰。

2. 目标对象/容器

这段代码的“pretty decoration”的目标对象obj是具有这两种属性的对象

重载std::begin和std::end定义(包括c风格数组), 通过ADL提供begin(obj)和end(obj), 类型为std::tuple 或std::pair类型。

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

代码在全局命名空间中提供操作符<<s,仅应用于没有更专用版本的操作符<<可用的类。 因此,例如std::string虽然具有有效的开始/结束对,但在这段代码中不会使用操作符打印。

3.利用和定制

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

A)默认装饰

默认前缀是""(什么都没有),默认后缀也是,而默认分隔符是","(逗号+空格)。

B)通过专门化漂亮的::defaults类模板定制一个类型的默认装饰

default结构体有一个静态成员函数decoration(),返回一个装潢对象,其中包含给定类型的默认值。

使用数组的示例:

自定义默认数组打印:

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)

对字符流使用PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX,…)宏

宏展开为

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

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

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

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

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

wchar_t流的另一个宏包括:PRETTY_DEFAULT_WDECORATION。

C)对溪流进行装饰

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

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

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

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

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

自定义给定流的分隔符

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

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

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

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

4. 特殊处理std::tuple

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

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

要返回给定类型的默认装饰,请在流上使用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_

重载操作符< <:

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

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

你可以使用cbegin和cend来使auto (c++ 11)/typedef版本为const

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

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

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

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

到标准输出(godbolt)。

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

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


从第一个BoostCon(现在叫CppCon)出来,我和另外两个人致力于一个库来实现这一点。主要的问题是需要扩展命名空间std。这对boost库来说是不可能的。

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

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;

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

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

/*! \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::vector、std::array和std::tuple

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

模板收集内容

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

此外,它还为std::string << container定义了<<运算符。 这样就可以以紧凑的方式构造包含这些容器类型的字符串。

From

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

我们会讲到

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

Code

您可以交互地测试这段代码:这里。

#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是一个选项,那么你可以使用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';

对于其他类型的向量,首先需要转换为字符串

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

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

Godbolt演示


我写了一个操作符<<,它输出任何可迭代对象,包括自定义容器、标准容器和具有已知边界的数组。需要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;
}

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

[1, 2, 3, 4]


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

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

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

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