довольно-принт с контейнерами stl


please take note of the updates at the end of this post.update: i have created a public project on github for this library!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;}

теперь я видел много шаблонов магия здесь, так что я никогда не думал, что это возможно, так что я интересно, если кто может подсказать то, что будет соответствовать всем с. контейнеры может что-то признака-ишь, что можно выяснить, если что-то имеет необходимые итератора?

большое спасибо!

update (and solution)channel 9

после снова поднимая эту проблему , я получил фантастический ответ от свен властелин, который, в сочетании с немного sfinae предательство типа, появляется, чтобы решить проблему в совершенно общей и вкладные моды. разделители могут быть индивидуально специализированный, пример специализацию для std::набор в комплекте, а также пример использования пользовательских разделителей.

update:

помощник "wrap_array()" можно использовать для печати необработанные массивы c#. пары и кортежи для печати

включить-если признак типа требуется c 0х, но с некоторыми модификациями можно сделать с 98 версии данной. кортежи нужны шаблоны с переменным числом аргументов, поэтому с 0х.

update:

я попросил свена опубликовать решение здесь, так что я могу принять его, но в то же время я бы хотел разместить код себе для справки. ( свен теперь разместил его код ниже, который я сделал принято отвечать. мой собственный код использует черты контейнерного типа, которые работают для меня, но может привести к неожиданному поведению с non-контейнерные классы, которые обеспечивают итераторы.)

header (prettyprint.h):
#ifndef h_pretty_print#define h_pretty_print#include <type_traits>#include <iostream>#include <utility>#include <tuple>namespace std{    // pre-declarations of container types so we don't actually have to include the relevant headers if not needed, speeding up compilation time.    template<typename t, typename ttraits, typename tallocator> class set;}namespace pretty_print{    // sfinae type trait to detect a container based on whether t::const_iterator exists.    // (improvement idea: check also if begin()/end() exist.)    template<typename t>    struct is_container_helper    {    private:        template<typename c> static char test(typename c::const_iterator*);        template<typename c> static int  test(...);    public:        static const bool value = sizeof(test<t>(0)) == sizeof(char);    };    // basic is_container template; specialize to derive from std::true_type for all desired container types    template<typename t> struct is_container : public ::std::integral_constant<bool, is_container_helper<t>::value> { };    // holds the delimiter values for a specific character type    template<typename tchar>    struct delimiters_values    {        typedef tchar char_type;        const tchar * prefix;        const tchar * delimiter;        const tchar * postfix;    };    // defines the delimiter values for a specific container and character type    template<typename t, typename tchar>    struct delimiters    {        typedef delimiters_values<tchar> type;        static const type values;     };    // default delimiters    template<typename t> struct delimiters<t, char> { static const delimiters_values<char> values; };    template<typename t> const delimiters_values<char> delimiters<t, char>::values = { "[", ", ", "]" };    template<typename t> struct delimiters<t, wchar_t> { static const delimiters_values<wchar_t> values; };    template<typename t> const delimiters_values<wchar_t> delimiters<t, wchar_t>::values = { l"[", l", ", l"]" };    // delimiters for set    template<typename t, typename ttraits, typename tallocator> struct delimiters< ::std::set<t, ttraits, tallocator>, char> { static const delimiters_values<char> values; };    template<typename t, typename ttraits, typename tallocator> const delimiters_values<char> delimiters< ::std::set<t, ttraits, tallocator>, char>::values = { "{", ", ", "}" };    template<typename t, typename ttraits, typename tallocator> struct delimiters< ::std::set<t, ttraits, tallocator>, wchar_t> { static const delimiters_values<wchar_t> values; };    template<typename t, typename ttraits, typename tallocator> const delimiters_values<wchar_t> delimiters< ::std::set<t, ttraits, tallocator>, wchar_t>::values = { l"{", l", ", l"}" };    // delimiters for pair (reused for tuple, see below)    template<typename t1, typename t2> struct delimiters< ::std::pair<t1, t2>, char> { static const delimiters_values<char> values; };    template<typename t1, typename t2> const delimiters_values<char> delimiters< ::std::pair<t1, t2>, char>::values = { "(", ", ", ")" };    template<typename t1, typename t2> struct delimiters< ::std::pair<t1, t2>, wchar_t> { static const delimiters_values<wchar_t> values; };    template<typename t1, typename t2> const delimiters_values<wchar_t> delimiters< ::std::pair<t1, t2>, wchar_t>::values = { l"(", l", ", l")" };    // functor to print containers. you can use this directly if you want to specificy a non-default delimiters type.    template<typename t, typename tchar = char, typename tchartraits = ::std::char_traits<tchar>, typename tdelimiters = delimiters<t, tchar>>    struct print_container_helper    {        typedef tchar char_type;        typedef tdelimiters delimiters_type;        typedef std::basic_ostream<tchar, tchartraits> & ostream_type;        print_container_helper(const t & container)        : _container(container)        {        }        inline void operator()(ostream_type & stream) const        {            if (delimiters_type::values.prefix != null)                stream << delimiters_type::values.prefix;            for (typename t::const_iterator beg = _container.begin(), end = _container.end(), it = beg; it != end; ++it)            {                if (it != beg && delimiters_type::values.delimiter != null)                    stream << delimiters_type::values.delimiter;                stream << *it;            }            if (delimiters_type::values.postfix != null)                stream << delimiters_type::values.postfix;        }    private:        const t & _container;    };    // type-erasing helper class for easy use of custom delimiters.    // requires tchartraits = std::char_traits<tchar> and tchar = char or wchar_t, and mydelims needs to be defined for tchar.    // usage: "cout << pretty_print::custom_delims<mydelims>(x)".    struct custom_delims_base    {        virtual ~custom_delims_base() { }        virtual ::std::ostream & stream(::std::ostream &) = 0;        virtual ::std::wostream & stream(::std::wostream &) = 0;    };    template <typename t, typename delims>    struct custom_delims_wrapper : public custom_delims_base    {        custom_delims_wrapper(const t & t) : t(t) { }        ::std::ostream & stream(::std::ostream & stream)        {          return stream << ::pretty_print::print_container_helper<t, char, ::std::char_traits<char>, delims>(t);        }        ::std::wostream & stream(::std::wostream & stream)        {          return stream << ::pretty_print::print_container_helper<t, wchar_t, ::std::char_traits<wchar_t>, delims>(t);        }    private:        const t & t;    };    template <typename delims>    struct custom_delims    {        template <typename container> custom_delims(const container & c) : base(new custom_delims_wrapper<container, delims>(c)) { }        ~custom_delims() { delete base; }        custom_delims_base * base;    };} // namespace pretty_printtemplate <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_printnamespace 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_printtemplate <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 containertemplate<> const pretty_print::delimiters_values<char> pretty_print::delimiters<std::vector<double>, char>::values = { "|| ", " : ", " ||" };// custom delimiters for one-off usestruct mydel { static const delimiters_values<char> values; };const delimiters_values<char> mydel::values = { "<", "; ", ">" };int main(int argc, char * argv[]){  std::string cs;  std::unordered_map<int, std::string> um;  std::map<int, std::string> om;  std::set<std::string> ss;  std::vector<std::string> v;  std::vector<std::vector<std::string>> vv;  std::vector<std::pair<int, std::string>> vp;  std::vector<double> vd;  v.reserve(argc - 1);  vv.reserve(argc - 1);  vp.reserve(argc - 1);  vd.reserve(argc - 1);  std::cout << "printing pairs." << std::endl;  while (--argc)  {    std::string s(argv[argc]);    std::pair<int, std::string> p(argc, s);    um[argc] = s;    om[argc] = s;    v.push_back(s);    vv.push_back(v);    vp.push_back(p);    vd.push_back(1./double(i));    ss.insert(s);    cs += s;    std::cout << "  " << p << std::endl;  }  std::array<char, 5> a{{ 'h', 'e', 'l', 'l', 'o' }};  std::cout << "vector: " << v << std::endl            << "incremental vector: " << vv << std::endl            << "another vector: " << vd << std::endl            << "pairs: " << vp << std::endl            << "set: " << ss << std::endl            << "omap: " << om << std::endl            << "umap: " << um << std::endl            << "string: " << cs << std::endl            << "array: " << a << std::endl  ;  // using custom delimiters manually:  std::cout << pretty_print::print_container_helper<std::vector<std::string>, char, std::char_traits<char>, mydel>(v) << std::endl;  // using custom delimiters with the type-erasing helper class  std::cout << pretty_print::custom_delims<mydel>(v) << std::endl;  // pairs and tuples and arrays:  auto a1 = std::make_pair(std::string("jello"), 9);  auto a2 = std::make_tuple(1729);  auto a3 = std::make_tuple("qrgh", a1, 11);  auto a4 = std::make_tuple(1729, 2875, std::pair<double, std::string>(1.5, "meow"));  int arr[] = { 1, 4, 9, 16 };  std::cout << "c array: " << wrap_array(arr) << std::endl            << "pair: " << a1 << std::endl            << "1-tuple: " << a2 << std::endl            << "n-tuple: " << a3 << std::endl            << "n-tuple: " << a4 << std::endl  ;}
further ideas for improvements:
  • implement output for std::tuple<...> in the same way is we have it for std::pair<s,t>. update: this is now a separate question on so! upupdate: this has now been implemented, thanks to xeo!
  • add namespaces so that the helper classes don't bleed into the global namespace. done
  • add template aliases (or something similar) to facilitate making custom delimiter classes, or maybe preprocessor macros?
  • recent updates:
  • i removed the custom output iterator in favour of a simple for loop in the print function.
  • all implementation details are now in the pretty_print namespace. only the global stream operators and the pretty_print_array wrapper are in the global namespace.
  • fixed the namespacing so that operator<< is now correctly in std.
  • notes:
  • removing the output iterator means that there is no way to use std::copy() to get pretty-printing. i might reinstate the pretty iterator if this is a desired feature, but sven's code below has the implementation.
  • it was a conscious design decision to make the delimiters compile-time constants rather than object constants. that means that you cannot supply delimiters dynamically at runtime, but it also means that there's no unneeded overhead. an object-based delimiter configuration has been proposed by dennis zickefoose in a comment to sven's code below. if desired, this could be implemented as an alternative feature.
  • it is currently not obvious how to customize nested container delimiters.
  • bear in mind that the purpose of this library is to allow quick container printing facilities that require zero coding on your part. it is not an all-purpose formatting library, but rather a developing tool to alleviate the need to write boiler-plate code for container inspection.
  • thank you to everyone who contributed!note: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;vmyprinter

    теперь мы хотим иметь возможность писать по некоторым контейнер с помощью этих разделителей. будет тип-стирание класс, вот так:

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