简体   繁体   English

漂亮的C ++ STL容器

[英]Pretty-print C++ STL containers

Please take note of the updates at the end of this post. 请注意本文末尾的更新。

Update: I have created a public project on GitHub for this library! 更新:我在GitHub上为这个库创建了一个公共项目


I would like to have a single template that once and for all takes care of pretty-printing all STL containers via operator<< . 我希望有一个模板,一劳永逸地通过operator<<所有STL容器的打印。 In pseudo code, I'm looking for something like this: 在伪代码中,我正在寻找这样的东西:

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

Now I've seen plenty of template magic here on SO that I never thought possible, so I'm wondering if anyone can suggest something that would match all containers C. Maybe something trait-ish that can figure out if something has the necessary iterator? 现在我在SO上看到了很多模板魔法,我从来没有想过可能,所以我想知道是否有人可以建议一些能匹配所有容器C的东西。也许某些特性可以判断出某些东西是否有必要的迭代器?

Many thanks! 非常感谢!


Update (and solution) 更新(和解决方案)

After raising this problem again on Channel 9 , I got a fantastic answer from Sven Groot, which, combined with a bit of SFINAE type traiting, appears to solve the problem in a completely general and nestable fashion. 第9频道再次提出这个问题之后,我从Sven Groot得到了一个很棒的答案,结合了一些SFINAE类型的特征,似乎以一种完全通用和可嵌套的方式解决了这个问题。 The delimiters may be individually specialised, an example specialization for std::set is included, as well as an example of using custom delimiters. 分隔符可以是单独的专用,包括std :: set的示例特化,以及使用自定义分隔符的示例。

The helper "wrap_array()" can be used to print raw C arrays. 帮助程序“wrap_array()”可用于打印原始C数组。 Update: Pairs and tuples are available for printing; 更新:对和元组可用于打印; default delimiters are round brackets. 默认分隔符是圆括号。

The enable-if type trait requires C++0x, but with some modifications it should be possible to make a C++98 version of this. enable-if类型特征需要C ++ 0x,但经过一些修改后,应该可以制作一个C ++ 98版本。 Tuples require variadic templates, hence C++0x. 元组需要可变参数模板,因此需要C ++ 0x。

I have asked Sven to post the solution here so that I can accept it, but in the meantime I'd like to post the code myself for reference. 我已经要求Sven在这里发布解决方案以便我可以接受它,但与此同时我想自己发布代码以供参考。 ( Update: Sven has now posted his code below, which I made the accepted answer. My own code uses container type traits, which work for me but may cause unexpected behaviour with non-container classes that provide iterators.) 更新: Sven现在已经在下面发布了他的代码,我做了接受的答案。我自己的代码使用容器类型特征,这对我有用但可能会导致非容器类提供迭代器的意外行为。)

Header (prettyprint.h): 标题(prettyprint.h):

#ifndef H_PRETTY_PRINT
#define H_PRETTY_PRINT


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


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

namespace pretty_print
{

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

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


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

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


    // Holds the delimiter values for a specific character type

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


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

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


    // Default delimiters

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


    // Delimiters for set

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


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

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


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

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

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

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

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

                stream << *it;
            }

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

    private:
        const T & _container;
    };


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

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

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

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

    private:
        const T & t;
    };

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

} // namespace pretty_print


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


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

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


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

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

    // Prints a container to the stream using default delimiters

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

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

        stream << value.first;

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

        stream << value.second;

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

        return stream;
    }
} // namespace std

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

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

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

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

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

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

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


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

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

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

        return stream;
    }
} // namespace std


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

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

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

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

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


#endif

Usage example: 用法示例:

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

#include "prettyprint.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

Further ideas for improvements: 进一步改进的想法:

  • Implement output for std::tuple<...> in the same way is we have it for std::pair<S,T> . 以与std::pair<S,T>相同的方式实现std::tuple<...>输出。 Update: This is now a separate question on SO ! 更新:这是一个关于SO单独问题 Upupdate: This has now been implemented, thanks to Xeo! Upupdate:由于Xeo,现在已经实现了!
  • Add namespaces so that the helper classes don't bleed into the global namespace. 添加名称空间,以便辅助类不会渗透到全局名称空间中。 Done 完成
  • Add template aliases (or something similar) to facilitate making custom delimiter classes, or maybe preprocessor macros? 添加模板别名(或类似的东西)以便于制作自定义分隔符类,或者可能是预处理器宏?

Recent updates: 最近更新:

  • I removed the custom output iterator in favour of a simple for loop in the print function. 我删除了自定义输出迭代器,转而使用print函数中的简单for循环。
  • All implementation details are now in the pretty_print namespace. 所有实现细节现在都在pretty_print命名空间中。 Only the global stream operators and the pretty_print_array wrapper are in the global namespace. 只有全局流操作符和pretty_print_array包装器位于全局命名空间中。
  • Fixed the namespacing so that operator<< is now correctly in std . 修复了命名空间,以便operator<<现在正确在std

Notes: 笔记:

  • Removing the output iterator means that there is no way to use std::copy() to get pretty-printing. 删除输出迭代器意味着无法使用std::copy()来进行漂亮的打印。 I might reinstate the pretty iterator if this is a desired feature, but Sven's code below has the implementation. 如果这是一个理想的功能,我可能会恢复漂亮的迭代器,但下面的Sven代码有实现。
  • It was a conscious design decision to make the delimiters compile-time constants rather than object constants. 这是一个有意识的设计决策,使分隔符编译时常量而不是对象常量。 That means that you cannot supply delimiters dynamically at runtime, but it also means that there's no unneeded overhead. 这意味着您无法在运行时动态提供分隔符,但这也意味着没有不必要的开销。 An object-based delimiter configuration has been proposed by Dennis Zickefoose in a comment to Sven's code below. Dennis Zickefoose在下面对Sven的代码的评论中提出了一种基于对象的分隔符配置。 If desired, this could be implemented as an alternative feature. 如果需要,这可以作为替代功能实现。
  • It is currently not obvious how to customize nested container delimiters. 目前尚不清楚如何自定义嵌套容器分隔符。
  • Bear in mind that the purpose of this library is to allow quick container printing facilities that require zero coding on your part. 请记住,此库的目的是允许快速容器打印设施,您需要零编码 It is not an all-purpose formatting library, but rather a developing tool to alleviate the need to write boiler-plate code for container inspection. 它不是一个通用的格式化库,而是一个开发工具,以减少编写用于容器检查的样板代码的需要。

Thank you to everyone who contributed! 谢谢所有贡献的人!


Note: If you are looking for a quick way to deploy custom delimiters, here is one way using type erasure. 注意:如果您正在寻找一种快速部署自定义分隔符的方法,这里有一种使用类型擦除的方法。 We assume that you have already constructed a delimiter class, say MyDel , like so: 我们假设你已经构建了一个分隔符类,比如MyDel ,就像这样:

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

Now we want to be able to write std::cout << MyPrinter(v) << std::endl; 现在我们希望能够编写std::cout << MyPrinter(v) << std::endl; for some container v using those delimiters. 对于某些容器v使用这些分隔符。 MyPrinter will be a type-erasing class, like so: 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); }

This solution was inspired by Marcelo's solution, with a few changes: 这个解决方案的灵感来自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;
}

Like Marcelo's version, it uses an is_container type trait that must be specialized for all containers that are to be supported. 与Marcelo的版本一样,它使用is_container类型特征,该特征必须专门用于所有要支持的容器。 It may be possible to use a trait to check for value_type , const_iterator , begin() / end() , but I'm not sure I'd recommend that since it might match things that match those criteria but aren't actually containers, like std::basic_string . 有可能使用特征来检查value_typeconst_iteratorbegin() / end() ,但我不确定我是否建议,因为它可能匹配符合这些条件但实际上不是容器的东西,比如std::basic_string Also like Marcelo's version, it uses templates that can be specialized to specify the delimiters to use. 与Marcelo的版本一样,它使用专门用于指定要使用的分隔符的模板。

The major difference is that I've built my version around a pretty_ostream_iterator , which works similar to the std::ostream_iterator but doesn't print a delimiter after the last item. 主要区别在于我围绕一个pretty_ostream_iterator构建了我的版本,它的工作方式类似于std::ostream_iterator但是在最后一个项目后没有打印分隔符。 Formatting the containers is done by the print_container_helper , which can be used directly to print containers without an is_container trait, or to specify a different delimiters type. 格式化容器由print_container_helper完成,可以直接用于打印没有is_container特征的容器,或者指定不同的分隔符类型。

I've also defined is_container and delimiters so it will work for containers with non-standard predicates or allocators, and for both char and wchar_t. 我还定义了is_container和delimiters,因此它适用于具有非标准谓词或分配器的容器,以及char和wchar_t。 The operator<< function itself is also defined to work with both char and wchar_t streams. operator <<函数本身也被定义为使用char和wchar_t流。

Finally, I've used std::enable_if , which is available as part of C++0x, and works in Visual C++ 2010 and g++ 4.3 (needs the -std=c++0x flag) and later. 最后,我使用了std::enable_if ,它是C ++ 0x的一部分,可用于Visual C ++ 2010和g ++ 4.3(需要-std = c ++ 0x标志)以及之后的版本。 This way there is no dependency on Boost. 这种方式不依赖于Boost。

This has been edited a few times, and we have decided to call the main class that wraps a collection RangePrinter 这已被编辑了几次,我们决定调用包装集合RangePrinter的主类

This should work automatically with any collection once you have written the one-time operator<< overload, except that you will need a special one for maps to print the pair, and may want to customise the delimiter there. 一旦您编写了一次性操作符<< overload,这将自动与任何集合一起工作,除了您需要一个特殊的地图来打印该对,并且可能想要在那里自定义分隔符。

You could also have a special "print" function to use on the item instead of just outputting it direct. 您还可以在项目上使用特殊的“打印”功能,而不是直接输出。 A bit like STL algorithms allow you to pass in custom predicates. 有点像STL算法允许您传递自定义谓词。 With map you would use it this way, with a custom printer for the std::pair. 使用map,您可以使用这种方式,使用std :: pair的自定义打印机。

Your "default" printer would just output it to the stream. 您的“默认”打印机只会将其输出到流。

Ok, let's work on a custom printer. 好吧,让我们使用自定义打印机。 I will change my outer class to RangePrinter. 我将我的外部类更改为RangePrinter。 So we have 2 iterators and some delimiters but have not customised how to print the actual items. 所以我们有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 :: set by

 std::cout << outputFormatter( mySet );

You can also write free-function version that take a custom printer and ones that take two iterators. 您还可以编写采用自定义打印机的自由功能版本和采用两个迭代器的自由功能版本。 In any case they will resolve the template parameters for you, and you will be able to pass them through as temporaries. 在任何情况下,他们都会为您解析模板参数,您将能够将它们作为临时传递。

Here is a working library, presented as a complete working program, that I just hacked together: 这是一个工作库,作为一个完整的工作程序呈现,我刚刚一起攻击:

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

#include <boost/utility/enable_if.hpp>

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

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

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

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

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

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

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

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

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

It currently only works with vector and set , but can be made to work with most containers, just by expanding on the IsContainer specializations. 它目前只适用于vectorset ,但只需扩展IsContainer ,就可以使用大多数容器。 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; 我不得不排除char数组以避免进一步的模糊; it might still get into trouble with wchar_t[] . wchar_t[]可能仍会遇到麻烦。

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. 因此,我决定在MIT许可下发布它,并提供一个GitHub存储库,其中可以下载标题和一个小示例文件。

http://djmuw.github.io/prettycc http://djmuw.github.io/prettycc

0. Preface and wording 0.前言和措辞

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). 前缀字符串插入到流之前和后缀字符串之后的容器值(请参阅2.目标容器)。 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 compiled time constant because runtime checks are required to check whether a custom decoration has been applied to the current stream. 注意:实际上,这个答案没有解决100%的问题,因为装饰不是严格编译的时间常数,因为需要运行时检查来检查自定义装饰是否已应用于当前流。 Nevertheless, I think it has some decent features. 不过,我认为它有一些不错的功能。

Note2: May have minor bugs since it is not yet well tested. 注2:可能有小错误,因为它尚未经过良好测试。

1. General idea/usage 1.一般想法/用法

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 a 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 它可以专门用于更改(a)某些类型的默认装饰,它是
  • 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. 使用ios_base使用xalloc / pword提供的私有存储来保存指向pretty::decor对象的指针,该对象专门装饰某个流上的某个类型。

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. 如果没有明确设置此流的pretty::decor<T>对象,则调用pretty::defaulted<T, charT, chartraitT>::decoration()以获取给定类型的默认装饰。 The class pretty::defaulted is to be specialized to customize default decorations. pretty::defaulted类专门用于自定义默认装饰。

2. Target objects / containers 2.目标对象/容器

Target objects obj for the 'pretty decoration' of this code are objects having either 此代码的“漂亮装饰”的目标对象obj是具有任何一个的对象

  • overloads std::begin and std::end defined (includes C-Style arrays), 重载std::beginstd::end defined(包括C-Style数组),
  • having begin(obj) and end(obj) available via ADL, 通过ADL获得begin(obj)end(obj)
  • are of type std::tuple std::tuple类型
  • or of type std::pair . 或者类型为std::pair

The code includes a trait for identification of classes with range features ( begin / end ). 该代码包括用于识别具有范围特征( begin / end )的类的特征。 (There's no check included, whether begin(obj) == end(obj) is a valid expression, though.) (不包括检查,但是begin(obj) == end(obj)是否是一个有效的表达式。)

The code provides operator<< s in the global namespace that only apply to classes not having a more specialized version of operator<< available. 该代码在全局命名空间中提供operator<< s,它仅适用于没有更专业版operator<<可用的类。 Therefore, for example std::string is not printed using the operator in this code although having a valid begin / end pair. 因此,例如,尽管具有有效的begin / end对,但在此代码中不使用运算符打印std::string

3. Utilization and customization 3.利用和定制

Decorations can be imposed separately for every type (except different tuple s) and stream (not stream type!). 可以为每种类型(不同的tuple除外)和流(不是流类型!)单独强加装饰。 (Ie a std::vector<int> can have different decorations for different stream objects.) (即std::vector<int>可以为不同的流对象提供不同的装饰。)

A) Default decoration A)默认装饰

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 B)通过专门化pretty::defaulted类模板来定制类型的默认装饰

The struct defaulted has a static member function decoration() returning a decor object which includes the default values for the given type. struct defaulted具有一个静态成员函数decoration()返回一个decor对象,该对象包含给定类型的默认值。

Example using an array: 使用数组的示例:

Customize 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: 打印arry数组:

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 char流使用PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)

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 . 包含wchar_t流的另一个宏: PRETTY_DEFAULT_WDECORATION

C) Impose decoration on streams C)在溪流上施加装饰

The function pretty::decoration is used to impose a decoration on a certain stream. 函数pretty::decoration用于在某个流上强加一个装饰。 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 4. 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. 此代码不是允许对每个可能的元组类型进行特化,而是将std::tuple<void*>可用的任何装饰应用于所有类型的std::tuple<...>

5. Remove custom decoration from the stream 5.从流中删除自定义装饰

To go back to the defaulted decoration for a given type use pretty::clear function template on the stream s . 要返回给定类型的默认装饰,请在流s上使用pretty::clear函数模板。

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

5. Further examples 5.进一步的例子

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;

Prints 打印

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

See it on ideone/KKUebZ ideone / KKUebZ上看到它

6. Code 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_

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 . 创建一个派生自std::locale::facet The slight downside is that you will need a compilation unit somewhere to hold its id. 稍微缺点是你需要一个编译单元来保存它的id。 Let's call it MyPrettyVectorPrinter. 我们称之为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 > 在您的流函数中,检查std::has_facet< MyPrettyVectorPrinter >
  3. If that returns true, extract it with std::use_facet< MyPrettyVectorPrinter >( os.getloc() ) 如果返回true,则使用std::use_facet< MyPrettyVectorPrinter >( os.getloc() )提取它
  4. Your facet objects will have values for the delimiters and you can read them. 您的facet对象将具有分隔符的值,您可以阅读它们。 If the facet isn't found, your print function ( operator<< ) provides default ones. 如果未找到构面,则打印功能( operator<< )提供默认值。 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. 我把它写成一个新的解决方案,而不是修改我的另一个,因为我相信这两种方法都是正确的,你可以选择。

You can format containers as well as ranges and tuples using the {fmt} library . 您可以使用{fmt}库格式化容器以及范围和元组。 For example: 例如:

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

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

prints 版画

{1, 2, 3}

to stdout . stdout

Disclaimer : I'm the author of {fmt}. 免责声明 :我是{fmt}的作者。

The goal here is to use ADL to do customization of how we pretty print. 这里的目标是使用ADL来定制我们如何打印。

You pass in a formatter tag, and override 4 functions (before, after, between and descend) in the tag's namespace. 传入格式化程序标记,并覆盖标记命名空间中的4个函数(之前,之后,之间和之后)。 This changes how the formatter prints 'adornments' when iterating over containers. 这会改变格式化程序在迭代容器时打印“装饰”的方式。

A default formatter that does {(a->b),(c->d)} for maps, (a,b,c) for tupleoids, "hello" for strings, [x,y,z] for everything else included. 对于地图执行{(a->b),(c->d)}的默认格式化程序,对于tupleoids执行(a,b,c) ,对于字符串执行"hello"对于包含的所有其他内容执行[x,y,z]

It should "just work" with 3rd party iterable types (and treat them like "everything else"). 它应该“正常工作”第三方可迭代类型(并将它们视为“其他所有”)。

If you want custom adornments for your 3rd party iterables, simply create your own tag. 如果您想要第三方迭代的自定义装饰,只需创建自己的标签即可。 It will take a bit of work to handle map descent (you need to overload pretty_print_descend( your_tag to return pretty_print::decorator::map_magic_tag<your_tag> ). Maybe there is a cleaner way to do this, not sure. 处理地图下降需要一些工作(你需要重载pretty_print_descend( your_tag以返回pretty_print::decorator::map_magic_tag<your_tag> )。也许有一种更pretty_print::decorator::map_magic_tag<your_tag>方法来做到这一点,不确定。

A little library to detect iterability, and tuple-ness: 一个用于检测迭代性和元组的小库:

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

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

A library that lets us visit the contents of an iterable or tuple type object: 一个允许我们访问可迭代或元组类型对象内容的库:

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

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

A pretty printing library: 一个漂亮的打印库:

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

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

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

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

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

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

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

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

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

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

Test code: 测试代码:

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

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

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

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

live example 实例

This does use C++14 features (some _t aliases, and auto&& lambdas), but none are essential. 这确实使用了C ++ 14的功能(一些_t别名和auto&& lambdas),但没有一个是必不可少的。

Coming out of one of the first BoostCon (now called CppCon), I and two others worked on a library to do just this. 来自第一个BoostCon(现在称为CppCon)之一,我和另外两个人在一个库上工作就是这样做的。 The main sticking point was needing to extend namespace std. 主要的关键点是需要扩展名称空间std。 That turned out to be a no-go for a boost library. 结果证明这对于一个提升库来说是不可取的。

Unfortunately the links to the code no longer work, but you might find some interesting tidbits in the discussions (at least those that aren't talking about what to name it!) 不幸的是,代码的链接不再起作用,但你可能会在讨论中找到一些有趣的花絮(至少那些没有谈论命名的东西!)

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

My solution is simple.h , which is part of scc package. 我的解决方案是simple.h ,它是scc包的一部分。 All std containers, maps, sets, c-arrays are printable. 所有标准容器,地图,集合,c阵列都是可打印的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM