templates stl常用容器 - 漂亮的C++STL容器




c++顺序容器 stl详解 (7)

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

一旦您编写了一次性操作符<<重载,这应该会自动与任何集合一起工作,除了您需要一个特殊映射来打印该对,并且可能需要在其中自定义分隔符。

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

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

好的,让我们来研究一下自定义打印机。 我会将我的外部类更改为RangePrinter。 所以我们有2个迭代器和一些分隔符,但没有定制如何打印实际项目。

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

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

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

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

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

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

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

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

};


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

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

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

    printer( os, *begin );

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

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

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

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

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

You could then use it for std::set by

 std::cout << outputFormatter( mySet );

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

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

更新:我在这个库GitHub创建了一个公共项目

我希望有一个单独的模板,通过operator<< ,一劳永逸地处理所有STL容器。 在伪代码中,我正在寻找像这样的东西:

template<container C, class T, String delim = ", ", String open = "[", String close = "]">
std::ostream & operator<<(std::ostream & o, const C<T> & x)
{
    o << open;
    // for (typename C::const_iterator i = x.begin(); i != x.end(); i++) /* Old-school */
    for (auto i = x.begin(); i != x.end(); i++)
    {
        if (i != x.begin()) o << delim;
        o << *i;
    }
    o << close;
    return o;
}

现在我已经在这里看到了很多模板魔法,我从来没有想过可能,所以我想知道是否有人可以提出一些可以匹配所有容器的东西。也许有一些特征可以确定是否有必要的迭代器?

非常感谢!

更新(和解决方案)

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

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

enable-if类型特征需要C ++ 0x,但有一些修改后,应该可以创建C ++ 98版本。 元组需要可变参数模板,因此需要C ++ 0x。

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

标题(prettyprint.h):

#ifndef H_PRETTY_PRINT
#define H_PRETTY_PRINT


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


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

namespace pretty_print
{

    // SFINAE type trait to detect a container based on whether T::const_iterator exists.
    // (Improvement idea: check also if begin()/end() exist.)

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


    // Basic is_container template; specialize to derive from std::true_type for all desired container types

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


    // Holds the delimiter values for a specific character type

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


    // Defines the delimiter values for a specific container and character type

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


    // Default delimiters

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


    // Delimiters for set

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


    // Delimiters for pair (reused for tuple, see below)

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


    // Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type.

    template<typename T, typename TChar = char, typename TCharTraits = ::std::char_traits<TChar>, typename TDelimiters = delimiters<T, TChar>>
    struct print_container_helper
    {
        typedef TChar char_type;
        typedef TDelimiters delimiters_type;
        typedef std::basic_ostream<TChar, TCharTraits> & ostream_type;

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

        inline void operator()(ostream_type & stream) const
        {
            if (delimiters_type::values.prefix != NULL)
                stream << delimiters_type::values.prefix;

            for (typename T::const_iterator beg = _container.begin(), end = _container.end(), it = beg; it != end; ++it)
            {
                if (it != beg && delimiters_type::values.delimiter != NULL)
                    stream << delimiters_type::values.delimiter;

                stream << *it;
            }

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

    private:
        const T & _container;
    };


    // Type-erasing helper class for easy use of custom delimiters.
    // Requires TCharTraits = std::char_traits<TChar> and TChar = char or wchar_t, and MyDelims needs to be defined for TChar.
    // Usage: "cout << pretty_print::custom_delims<MyDelims>(x)".

    struct custom_delims_base
    {
        virtual ~custom_delims_base() { }
        virtual ::std::ostream & stream(::std::ostream &) = 0;
        virtual ::std::wostream & stream(::std::wostream &) = 0;
    };

    template <typename T, typename Delims>
    struct custom_delims_wrapper : public custom_delims_base
    {
        custom_delims_wrapper(const T & t) : t(t) { }

        ::std::ostream & stream(::std::ostream & stream)
        {
          return stream << ::pretty_print::print_container_helper<T, char, ::std::char_traits<char>, Delims>(t);
        }
        ::std::wostream & stream(::std::wostream & stream)
        {
          return stream << ::pretty_print::print_container_helper<T, wchar_t, ::std::char_traits<wchar_t>, Delims>(t);
        }

    private:
        const T & t;
    };

    template <typename Delims>
    struct custom_delims
    {
        template <typename Container> custom_delims(const Container & c) : base(new custom_delims_wrapper<Container, Delims>(c)) { }
        ~custom_delims() { delete base; }
        custom_delims_base * base;
    };

} // namespace pretty_print


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


// Template aliases for char and wchar_t delimiters
// Enable these if you have compiler support
//
// Implement as "template<T, C, A> const sdelims::type sdelims<std::set<T,C,A>>::values = { ... }."

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


namespace std
{
    // Prints a print_container_helper to the specified stream.

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

    // Prints a container to the stream using default delimiters

    template<typename T, typename TChar, typename TCharTraits>
    inline typename enable_if< ::pretty_print::is_container<T>::value, basic_ostream<TChar, TCharTraits>&>::type
    operator<<(basic_ostream<TChar, TCharTraits> & stream, const T & container)
    {
        return stream << ::pretty_print::print_container_helper<T, TChar, TCharTraits>(container);
    }

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

        stream << value.first;

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

        stream << value.second;

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

        return stream;
    }
} // namespace std

// Prints a tuple to the stream using delimiters from delimiters<std::pair<tuple_dummy_t, tuple_dummy_t>>.

namespace pretty_print
{
    struct tuple_dummy_t { }; // Just if you want special delimiters for tuples.

    typedef std::pair<tuple_dummy_t, tuple_dummy_t> tuple_dummy_pair;

    template<typename Tuple, size_t N, typename TChar, typename TCharTraits>
    struct pretty_tuple_helper
    {
        static inline void print(::std::basic_ostream<TChar, TCharTraits> & stream, const Tuple & value)
        {
            pretty_tuple_helper<Tuple, N - 1, TChar, TCharTraits>::print(stream, value);

            if (delimiters<tuple_dummy_pair, TChar>::values.delimiter != NULL)
                stream << delimiters<tuple_dummy_pair, TChar>::values.delimiter;

            stream << std::get<N - 1>(value);
        }
    };

    template<typename Tuple, typename TChar, typename TCharTraits>
    struct pretty_tuple_helper<Tuple, 1, TChar, TCharTraits>
    {
        static inline void print(::std::basic_ostream<TChar, TCharTraits> & stream, const Tuple & value) { stream << ::std::get<0>(value); }
    };
} // namespace pretty_print


namespace std
{
    template<typename TChar, typename TCharTraits, typename ...Args>
    inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const tuple<Args...> & value)
    {
        if (::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.prefix != NULL)
            stream << ::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.prefix;

        ::pretty_print::pretty_tuple_helper<const tuple<Args...> &, sizeof...(Args), TChar, TCharTraits>::print(stream, value);

        if (::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.postfix != NULL)
            stream << ::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.postfix;

        return stream;
    }
} // namespace std


// A wrapper for raw C-style arrays. Usage: int arr[] = { 1, 2, 4, 8, 16 };  std::cout << wrap_array(arr) << ...

namespace pretty_print
{
    template <typename T, size_t N>
    struct array_wrapper
    {
        typedef const T * const_iterator;
        typedef T value_type;

        array_wrapper(const T (& a)[N]) : _array(a) { }
        inline const_iterator begin() const { return _array; }
        inline const_iterator end() const { return _array + N; }

    private:
        const T * const _array;
    };
} // namespace pretty_print

template <typename T, size_t N>
inline pretty_print::array_wrapper<T, N> pretty_print_array(const T (& a)[N])
{
    return pretty_print::array_wrapper<T, N>(a);
}


#endif

用法示例:

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

#include "prettyprint.h"

// Specialization for a particular container
template<> const pretty_print::delimiters_values<char> pretty_print::delimiters<std::vector<double>, char>::values = { "|| ", " : ", " ||" };

// Custom delimiters for one-off use
struct MyDel { static const delimiters_values<char> values; };
const delimiters_values<char> MyDel::values = { "<", "; ", ">" };

int main(int argc, char * argv[])
{
  std::string cs;
  std::unordered_map<int, std::string> um;
  std::map<int, std::string> om;
  std::set<std::string> ss;
  std::vector<std::string> v;
  std::vector<std::vector<std::string>> vv;
  std::vector<std::pair<int, std::string>> vp;
  std::vector<double> vd;
  v.reserve(argc - 1);
  vv.reserve(argc - 1);
  vp.reserve(argc - 1);
  vd.reserve(argc - 1);

  std::cout << "Printing pairs." << std::endl;

  while (--argc)
  {
    std::string s(argv[argc]);
    std::pair<int, std::string> p(argc, s);

    um[argc] = s;
    om[argc] = s;
    v.push_back(s);
    vv.push_back(v);
    vp.push_back(p);
    vd.push_back(1./double(i));
    ss.insert(s);
    cs += s;

    std::cout << "  " << p << std::endl;
  }

  std::array<char, 5> a{{ 'h', 'e', 'l', 'l', 'o' }};

  std::cout << "Vector: " << v << std::endl
            << "Incremental vector: " << vv << std::endl
            << "Another vector: " << vd << std::endl
            << "Pairs: " << vp << std::endl
            << "Set: " << ss << std::endl
            << "OMap: " << om << std::endl
            << "UMap: " << um << std::endl
            << "String: " << cs << std::endl
            << "Array: " << a << std::endl
  ;

  // Using custom delimiters manually:
  std::cout << pretty_print::print_container_helper<std::vector<std::string>, char, std::char_traits<char>, MyDel>(v) << std::endl;

  // Using custom delimiters with the type-erasing helper class
  std::cout << pretty_print::custom_delims<MyDel>(v) << std::endl;

  // Pairs and tuples and arrays:
  auto a1 = std::make_pair(std::string("Jello"), 9);
  auto a2 = std::make_tuple(1729);
  auto a3 = std::make_tuple("Qrgh", a1, 11);
  auto a4 = std::make_tuple(1729, 2875, std::pair<double, std::string>(1.5, "meow"));
  int arr[] = { 1, 4, 9, 16 };

  std::cout << "C array: " << wrap_array(arr) << std::endl
            << "Pair: " << a1 << std::endl
            << "1-tuple: " << a2 << std::endl
            << "n-tuple: " << a3 << std::endl
            << "n-tuple: " << a4 << std::endl
  ;
}

进一步的改进想法:

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

最近更新:

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

笔记:

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

感谢所有贡献的人!

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

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

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

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

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

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

template <typename Delims>
std::ostream & operator<<(std::ostream & o, const MyPrinter<Delims> & p) { return p.base->stream(o); }

该解决方案受到Marcelo解决方案的启发,并进行了一些更改:

#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 <<函数本身也被定义为可以同时使用char和wchar_t流。

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


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


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

http://djmuw.github.io/prettycc

0. Preface and wording

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

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

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

1. General idea / usage

Zero additional code required for usage

It is to be kept as easy as

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

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

Easy customization ...

... with respect to specific stream object

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

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

or with respect to all streams:

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

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

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

Rough description

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

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

2. Target objects / containers

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

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

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

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

3. Utilization and customization

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

A) Default decoration

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

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

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

Example using array:

Cutomize default array printing:

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

Print an arry array:

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

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

The macro expands to

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

enabling the above partial specialization to be rewritten to

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

or inserting a full specialization like

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

Another macro for wchar_t streams is included: PRETTY_DEFAULT_WDECORATION .

C) Impose decoration on streams

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

Complete decoration for given type and stream

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

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

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

Customization of delimiter for given stream

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

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

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

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

4. Special handling of std::tuple

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

5. Remove custom decoration from stream

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

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

5. Further examples

Printing "matrix-like" with newline delimiter

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

打印

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

See it on ideone/KKUebZ

6. Code

#ifndef pretty_print_0x57547_sa4884X_0_1_h_guard_
#define pretty_print_0x57547_sa4884X_0_1_h_guard_

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

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

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

namespace pretty
{

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

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

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

  namespace detail
  {

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

    template<class T> struct clearer {};

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

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

  }

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

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

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

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

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

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

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

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

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

}

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

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

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


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

#endif // pretty_print_0x57547_sa4884X_0_1_h_guard_

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

The basics are here

Essentially what you do is:

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

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

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

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


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

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

#include <boost/utility/enable_if.hpp>

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

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

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

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

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

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

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

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

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

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

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


初学者

介绍,没有以前的编程经验

  • C ++ Primer *(Stanley Lippman,JoséeLajoie和Barbara E. Moo)( 针对C ++ 11更新 )以1k页为单位,这是对C ++的一个非常全面的介绍,它以非常易于访问的格式涵盖了语言中的所有内容而且非常详细。 第五版(2012年8月16日发布)涵盖了C ++ 11。 [Review]

  • 编程:使用C ++的原理和实践 (Bjarne Stroustrup,第2版 - 2014年5月25日)( 针对C ++ 11 / C ++更新14 )介绍使用C ++编程的语言创建者。 一个很好的阅读,假设没有以前的编程经验,但不仅适合初学者。

*不要与C ++ Primer Plus (Stephen Prata)混淆, review明显不太好。

介绍,具有以前的编程经验

  • C ++之旅 (Bjarne Stroustrup)( C ++ 17第2版 )“tour”是一本快速的(大约180页和14章)教程概述所有标准C ++(语言和标准库, 以及使用C ++) 11 )对于已经了解C ++或至少是经验丰富的程序员的人来说处于中等水平。 本书是构成C ++编程语言第4版第2-5章的材料的扩展版本。

  • 加速C ++ (Andrew Koenig和Barbara Moo,第1版 - 2000年8月24日)这基本上涵盖了与C ++ Primer相同的基础,但是它的四分之一空间。 这很大程度上是因为它并不试图成为编程的介绍,而是为以前用其他语言编程的人们介绍C ++ 。 它有一个更陡峭的学习曲线,但是,对于那些能够应对这种情况的人来说,它是一种非常紧凑的语言介绍。 (从历史上看,它作为第一本使用现代方法教授语言的初学者书开辟了新天地。)尽管如此,它所教授的C ++纯粹是C ++ 98。 [Review]

最佳做法

  • 有效的C ++ (Scott Meyers,第3版 - 2005年5月22日)这篇文章的目的是成为C ++程序员应该阅读的最好的第二本书,并且它成功了。 早期版本的目标是来自C的程序员,第三版改变了这一点,并针对来自Java等语言的程序员。 它提供了大约50个易于记忆的经验法则以及它们在非常容易获得(和愉快)风格中的基本原理。 对于C ++ 11和C ++ 14,示例和一些问题已经过时,应该首选Effective Modern C ++。 [Review]

  • Effective Modern C ++ (Scott Meyers)这基本上是Effective C ++的新版本,旨在让C ++程序员从C ++ 03转换到C ++ 11和C ++ 14。

  • 有效的STL (Scott Meyers)这个目标是对来自STL的标准库的部分做同样的事情,即Effective C ++对整个语言的作用:它提供了经验法则及其基本原理。 [Review]

中间

  • 更有效的C ++ (Scott Meyers)比Effective C ++更有经验法则。 没有第一本书那么重要,但仍然很有用。

  • Exceptional C ++ (Herb Sutter)作为一组谜题,通过资源获取初始化(RAII)以及对各种类型的深入报道,对C ++中适当的资源管理和异常安全进行了最佳和彻底的讨论。其他主题包括pimpl成语,名称查找,良好的类设计和C ++内存模型。 [Review]

  • 更多特殊C ++ (Herb Sutter)除了讨论C ++中有效的面向对象编程和正确使用STL之外,还涵盖了Exceptional C ++中未涉及的其他异常安全主题。 [Review]

  • 特殊的C ++风格 (Herb Sutter)讨论通用编程,优化和资源管理; 本书还对如何使用非成员函数和单一责任原则在C ++中编写模块化代码进行了很好的阐述。 [Review]

  • C ++编码标准 (Herb Sutter和Andrei Alexandrescu)这里的“编码标准”并不意味着“我应该在多少空格中缩进我的代码?”这本书包含了101个最佳实践,成语和常见的陷阱,可以帮助你写出正确的,可理解且高效的C ++代码。 [Review]

  • C ++模板:完整指南 (David Vandevoorde和Nicolai M. Josuttis)这是关于在C ++ 11之前存在的模板书。 它涵盖了从基础知识到一些最先进的模板元编程的所有内容,并解释了模板如何工作的每个细节(概念上和它们如何实现),并讨论了许多常见的陷阱。 在附录中有一个定义规则(ODR)和重载决策的优秀摘要。 第二版已经发布了涵盖C ++ 11,C ++ 14和C ++ 17的版本。 [Review]

高级

  • 现代C ++设计 (Andrei Alexandrescu)一本关于高级通用编程技术的开创性着作。 介绍基于策略的设计,类型列表和基本的通用编程习惯,然后解释有多少有用的设计模式(包括小对象分配器,仿函数,工厂,访问者和多方法)可以使用通用编程高效,模块化和干净地实现。 [Review]

  • C ++模板元编程 (David Abrahams和Aleksey Gurtovoy)

  • C ++ Concurrency In Action (Anthony Williams)一本涵盖C ++ 11并发支持的书,包括线程库,原子库,C ++内存模型,锁和互斥,以及设计和调试多线程应用程序的问题。

  • 高级C ++元编程 (Davide Di Gennaro)TMP技术的前C ++ 11手册,更多地关注实践而不是理论。 本书中有大量的片段,其中一些片段特征已经过时,但这些技巧仍然有用。 如果你能忍受古怪的格式/编辑,它比Alexandrescu更容易阅读,并且可以说更有价值。 对于更有经验的开发人员来说,很有可能你会发现一些关于C ++(一个怪癖)的黑暗角落,这通常只是通过丰富的经验来实现的。

参考风格 - 所有级别

  • C ++编程语言 (Bjarne Stroustrup)( 针对C ++ 11更新 )其创建者对C ++的经典介绍。 写得与经典的K&R并行,这确实非常像它,涵盖了从核心语言到标准库,编程范式到语言哲学的所有内容。 [Review]注意:在这个问题中跟踪了C ++标准的所有版本: 我在哪里可以找到当前的C ++标准 。

  • C ++标准库教程和参考 (Nicolai Josuttis)( 针对C ++ 11更新 )C ++标准库介绍和参考。 第二版(2012年4月9日发布)涵盖了C ++ 11。 [Review]

  • C ++ IO流和语言环境 (Angelika Langer和Klaus Kreft)除了如果您想了解有关流和语言环境的任何内容之外,对本书几乎没有什么可说的,那么这是找到明确答案的地方。 [Review]

C ++ 11/14/17 / ...参考文献:

  • C ++ 11/14/17标准(INCITS / ISO / IEC 14882:2011/2014/2017)当然,这是所有C ++的最终仲裁者。 但请注意,它纯粹是为有经验的用户提供参考, 他们愿意花费大量的时间和精力来理解它。 C ++ 17标准以198瑞士法郎的电子形式发布。

  • C ++ 17标准可用,但似乎不是经济形式 - 17它的成本为198瑞士法郎(约合200美元)。 对于大多数人来说, 标准化之前最终草案绰绰有余(而且是免费的)。 许多人更喜欢更新的草案 ,记录可能包含在C ++ 20中的新功能。

  • 新C ++概述(C ++ 11/14)( 仅限PDF) (Scott Meyers)( 针对C ++ 1y / C ++ 14更新 )这些是三个的演示材料(幻灯片和一些讲义)由Scott Meyers提供的日间培训课程,他是C ++的备受尊敬的作者。 即使项目清单很短,质量也很高。

  • C ++核心指南(C ++ 11/14/17 / ...) (由Bjarne Stroustrup和Herb Sutter编辑)是一个不断发展的在线文档,包含一套很好地使用现代C ++的指南。 指南侧重于相对较高级别的问题,例如接口,资源管理,内存管理以及影响应用程序架构和库设计的并发性。 该项目由Bjarne Stroustrup和其他人在CppCon'15宣布,并欢迎社区的贡献。 大多数指南都补充了基本原理和示例,以及对可能的工具支持的讨论。 许多规则专门设计为可由静态分析工具自动检查。

  • C ++ Super-FAQ (Marshall Cline,Bjarne Stroustrup等)是标准C ++基金会的一项努力,旨在统一以前由Marshall Cline和Bjarne Stroustrup单独维护的C ++常见问题解答,并纳入新的贡献。 这些项目主要针对中级问题,并且通常以幽默的语调编写。 并非所有项目都可能与最新版本的C ++标准完全一致。

  • cppreference.com(C ++ 03/11/14/17 / ...) (由Nate Kohl发起)是一个wiki,它总结了基本的核心语言特性,并提供了大量的C ++标准库文档。 文档非常精确,但比官方标准文档更易于阅读,并且由于其维基性质而提供更好的导航。 该项目记录了C ++标准的所有版本,该站点允许过滤特定版本的显示。 该项目由Nate Kohl在CppCon'14上展示

经典/老年人

注意:这些书中包含的某些信息可能不是最新的或不再被视为最佳做法。

  • C ++的设计和演变 (Bjarne Stroustrup)如果你想知道为什么语言是这样的,那么这本书就是你找到答案的地方。 这涵盖了C ++ 标准化之前的所有内容。

  • 关于C ++的反思 - (Andrew Koenig和Barbara Moo) [Review]

  • 高级C ++编程风格和习语 (James Coplien)模式运动的前身,它描述了许多特定于C ++的“成语”。 这肯定是一本非常好的书,如果你可以节省时间,可能仍然值得阅读,但是相当陈旧,并且不能与当前的C ++保持同步。

  • 大规模C ++软件设计 (John Lakos)Lakos解释了管理非常大的C ++软件项目的技术。 当然,这是一个很好的阅读,如果它只是最新的。 它早在C ++ 98之前编写,并且错过了许多对大型项目很重要的功能(例如命名空间)。 如果你需要在一个大的C ++软件项目中工作,你可能想要阅读它,尽管你需要花费更多的东西。 新版本的第一卷预计在2018年

  • 在C ++对象模型中 (Stanley Lippman)如果你想知道虚拟成员函数是如何被普遍实现的,以及基本对象在多继承场景中如何通常在内存中布局,以及所有这些如何影响性能,这就是你要去的地方找到对这些主题的深入讨论。

  • Annotated C ++参考手册 (Bjarne Stroustrup,Margaret A. Ellis)这本书已经过时了,因为它探讨了1989 C ++ 2.0版本 - 模板,异常,名称空间和新演员表尚未引入。 然而,这本书说明了本书贯穿整个C ++标准,解释了该语言的基本原理,可能的实现和功能。 这不是一本学习C ++编程原理和模式的书,而是了解C ++语言的各个方面。

  • 用C ++思考 (Bruce Eckel,第2版,2000)。 两卷; 是一套免费的教程风格的入门级书籍。 下载: 第1 第2卷 。 不幸的是,他们被一些微不足道的错误所破坏(例如,保持临时状态是自动的),没有正式的勘误列表。 部分第三方勘误列表可在( http://www.computersciencelab.com/Eckel.htm )获得,但显然没有维护。

  • 科学与工程C ++:高级技术与实例介绍 (John Barton和Lee Nackman)这是一本全面而且非常详细的书,试图在数值方法的背景下解释和利用C ++中的所有可用功能。 它在当时引入了几种新技术,例如奇怪的重复模板模式(CRTP,也称为Barton-Nackman技巧)。 它开创了几种技术,如尺寸分析和自动微分。 它附带了许多可编译和有用的代码,从表达式解析器到Lapack包装器。 该代码仍可在此处获取: http://www.informit.com/store/scientific-and-engineering-c-plus-plus-an-introduction-9780201533934http://www.informit.com/store/scientific-and-engineering-c-plus-plus-an-introduction-9780201533934 。 不幸的是,这些书在风格和C ++特性方面已经过时了,但是,当时它是一个令人难以置信的巡回演出(1994年,STL之前)。 关于动力学继承的章节有点复杂,不太有用。 这本经典书籍的更新版本包括移动语义和从STL学到的经验教训将是非常好的。





c++ templates c++11 operator-overloading pretty-print