Hộp đựng C ++ STL in đẹp


389

Xin lưu ý các bản cập nhật ở cuối bài này.

Cập nhật: Tôi đã tạo một dự án công khai trên GitHub cho thư viện này!


Tôi muốn có một mẫu duy nhất một lần và mãi mãi là việc in đẹp tất cả các container STL thông qua operator<<. Trong mã giả, tôi đang tìm kiếm một cái gì đó như thế này:

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

Bây giờ tôi đã thấy rất nhiều phép thuật mẫu ở đây trên SO mà tôi chưa bao giờ nghĩ là có thể, vì vậy tôi tự hỏi liệu có ai có thể đề xuất thứ gì đó phù hợp với tất cả các container không. Có lẽ một đặc điểm nào đó có thể tìm ra nếu có thứ gì đó có trình vòng lặp cần thiết ?

Cảm ơn nhiều!


Cập nhật (và giải pháp)

Sau khi nêu lại vấn đề này trên Kênh 9 , tôi đã nhận được câu trả lời tuyệt vời từ Sven Groot, kết hợp với một chút đặc điểm của SFINAE, xuất hiện để giải quyết vấn đề theo cách hoàn toàn chung và có thể lồng được. Các dấu phân cách có thể được chuyên biệt riêng, bao gồm một chuyên môn ví dụ cho std :: set, cũng như một ví dụ về việc sử dụng các dấu phân cách tùy chỉnh.

Trình trợ giúp "quấn_array ()" có thể được sử dụng để in các mảng C thô. Cập nhật: Cặp và bộ dữ liệu có sẵn để in; dấu phân cách mặc định là dấu ngoặc tròn.

Đặc điểm loại enable-if yêu cầu C ++ 0x, nhưng với một số sửa đổi, có thể tạo phiên bản C ++ 98 này. Tuples yêu cầu các mẫu matrixdic, do đó C ++ 0x.

Tôi đã yêu cầu Sven đăng giải pháp ở đây để tôi có thể chấp nhận nó, nhưng trong lúc này tôi muốn tự mình đăng mã để tham khảo. ( Cập nhật: Sven giờ đây đã đăng tải mã của ông dưới đây, mà tôi đưa ra câu trả lời được chấp nhận mã riêng của tôi sử dụng những đặc điểm loại container, mà làm việc cho tôi nhưng có thể gây ra hành vi bất ngờ với các lớp không chứa cung cấp vòng lặp..)

Tiêu đề (beautifulprint.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

Ví dụ sử dụng:

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

Những ý tưởng khác để cải tiến:

  • Thực hiện đầu ra cho std::tuple<...>cùng một cách là chúng tôi có nó cho std::pair<S,T>. Cập nhật: Đây là một câu hỏi riêng biệt trên SO ! Upupdate: Điều này hiện đã được thực hiện, nhờ Xeo!
  • Thêm không gian tên để các lớp trình trợ giúp không bị chảy vào không gian tên toàn cầu. Làm xong
  • Thêm các bí danh mẫu (hoặc một cái gì đó tương tự) để tạo điều kiện cho việc tạo các lớp phân cách tùy chỉnh hoặc có thể là các macro tiền xử lý?

Cập nhật gần đây:

  • Tôi đã loại bỏ trình lặp đầu ra tùy chỉnh theo hướng đơn giản cho vòng lặp trong chức năng in.
  • Tất cả các chi tiết thực hiện hiện đang ở trong pretty_printkhông gian tên. Chỉ các toán tử luồng toàn cầu và pretty_print_arraytrình bao bọc nằm trong không gian tên toàn cục.
  • Đã sửa lỗi không gian tên để operator<<bây giờ chính xác std.

Ghi chú:

  • Loại bỏ trình lặp đầu ra có nghĩa là không có cách nào để sử dụng std::copy()để có được bản in đẹp. Tôi có thể khôi phục trình lặp đẹp nếu đây là một tính năng mong muốn, nhưng mã của Sven bên dưới có triển khai.
  • Đó là một quyết định thiết kế có ý thức để làm cho các dấu phân cách biên dịch các hằng số thời gian hơn là các hằng số đối tượng. Điều đó có nghĩa là bạn không thể cung cấp các dấu phân cách động trong thời gian chạy, nhưng điều đó cũng có nghĩa là không có chi phí không cần thiết. Một cấu hình dấu phân cách dựa trên đối tượng đã được Dennis Zickefoose đề xuất trong một nhận xét về mã của Sven bên dưới. Nếu muốn, điều này có thể được thực hiện như một tính năng thay thế.
  • Hiện tại không rõ ràng làm thế nào để tùy chỉnh các dấu phân cách container lồng nhau.
  • Hãy nhớ rằng mục đích của thư viện này là cho phép các cơ sở in container nhanh chóng yêu cầu mã hóa bằng 0 từ phía bạn. Nó không phải là một thư viện định dạng đa mục đích, mà là một công cụ phát triển để giảm bớt sự cần thiết phải viết mã nồi hơi để kiểm tra container.

Cảm ơn tất cả những người đã đóng góp!


Lưu ý: Nếu bạn đang tìm kiếm một cách nhanh chóng để triển khai các dấu phân cách tùy chỉnh, đây là một cách sử dụng kiểu xóa. Chúng tôi giả sử rằng bạn đã xây dựng một lớp phân cách MyDel, như vậy:

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

Bây giờ chúng tôi muốn có thể viết std::cout << MyPrinter(v) << std::endl;cho một số container vbằng cách sử dụng các dấu phân cách đó. MyPrintersẽ là một lớp xóa kiểu, như vậy:

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

Mã của bạn sẽ không hoạt động. không có từ khóa nào như container C
the_drow

31
@the_drow: Có vẻ như OP đã biết điều đó rồi. Họ chỉ cho biết những gì họ đang tìm kiếm.
Marcelo Cantos

Thật vậy, tôi chỉ đưa ra một ví dụ mã giả "đạo đức". (Tôi cũng bỏ qua kiểu trả về, tôi đang lưu ý.) Để chắc chắn, tôi thậm chí không biết cách tốt nhất để làm cho các dấu phân cách có thể thay đổi.
Kerrek SB

1
Một cách khác là đặt các toán tử vào pretty_printkhông gian tên và cung cấp trình bao bọc cho người dùng sử dụng khi in. Từ quan điểm người dùng: std::cout << pretty_print(v);(có thể với một tên khác). Sau đó, bạn có thể cung cấp toán tử trong cùng một không gian tên như trình bao bọc, và sau đó nó có thể mở rộng thành in đẹp bất cứ thứ gì bạn muốn. Bạn cũng có thể nâng cao trình bao bọc cho phép tùy ý xác định dấu phân cách để sử dụng trong mỗi cuộc gọi (thay vì sử dụng các đặc điểm buộc cùng lựa chọn cho toàn bộ ứng dụng) \
David Rodríguez - dribeas

1
Vui lòng đặt câu trả lời "cập nhật" của bạn thành câu trả lời thực tế thay vì có một câu hỏi hóc búa.
einpoklum

Câu trả lời:


82

Giải pháp này được lấy cảm hứng từ giải pháp của Marcelo, với một vài thay đổi:

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

Giống như phiên bản của Marcelo, nó sử dụng một đặc điểm loại is_container phải chuyên biệt cho tất cả các container được hỗ trợ. Có thể sử dụng một đặc điểm để kiểm tra value_type, const_iterator, begin()/ end(), nhưng tôi không chắc là tôi muốn khuyên rằng kể từ khi nó có thể phù hợp với những điều mà phù hợp với những tiêu chí nhưng không thực sự container, giống như std::basic_string. Cũng giống như phiên bản của Marcelo, nó sử dụng các mẫu có thể chuyên biệt để chỉ định các dấu phân cách sử dụng.

Sự khác biệt chính là tôi đã xây dựng phiên bản của mình xung quanh một phiên bản pretty_ostream_iteratorhoạt động tương tự std::ostream_iteratornhưng không in dấu phân cách sau mục cuối cùng. Việc định dạng các thùng chứa được thực hiện bởi print_container_helper, có thể được sử dụng trực tiếp để in các thùng chứa mà không có đặc điểm is_container hoặc để chỉ định một loại dấu phân cách khác.

Tôi cũng đã định nghĩa is_container và dấu phân cách để nó sẽ hoạt động cho các thùng chứa với các biến vị ngữ hoặc phân bổ không chuẩn và cho cả char và wchar_t. Hàm toán tử << chính nó cũng được xác định để hoạt động với cả hai luồng char và wchar_t.

Cuối cùng, tôi đã sử dụng std::enable_if, có sẵn như là một phần của C ++ 0x và hoạt động trong Visual C ++ 2010 và g ++ 4.3 (cần cờ -std = c ++ 0x) trở lên. Cách này không có sự phụ thuộc vào Boost.


Nếu tôi đọc đúng, để có một cặp in như <i, j>trong một chức năng và như trong một chức năng [i j]khác, bạn phải xác định một loại hoàn toàn mới, với một số thành viên tĩnh để chuyển loại đó sang print_container_helper? Điều đó dường như quá phức tạp. Tại sao không đi với một đối tượng thực tế, với các trường bạn có thể đặt theo từng trường hợp và các chuyên ngành chỉ đơn giản cung cấp các giá trị mặc định khác nhau?
Dennis Zickefoose

Xem nó theo cách này: Nếu có một loạt các dấu phân cách mà bạn thích cá nhân, bạn có thể tạo một vài lớp với các thành viên tĩnh một lần và sau đó chỉ cần sử dụng chúng. Tất nhiên, bạn đúng khi sử dụng print_container_helperkhông thanh lịch như chỉ operator<<. Tất nhiên, bạn luôn có thể thay đổi nguồn, hoặc chỉ cần thêm các chuyên ngành rõ ràng cho vùng chứa yêu thích của bạn, ví dụ: cho pair<int, int>và cho pair<double, string>. Cuối cùng, đó là vấn đề cân nhắc sức mạnh chống lại sự thuận tiện. Đề nghị cải tiến chào mừng!
Kerrek SB

... Và để theo dõi điều đó, nếu bạn đã cần in tình huống cùng loại dữ liệu theo các định dạng khác nhau, có thể bạn sẽ phải viết ít nhất một trình bao bọc nhỏ. Đây không phải là thư viện định dạng có cấu hình cao, mà là thư viện mặc định hợp lý, không tốn công sức, cho phép bạn in các thùng chứa mà không cần suy nghĩ ... (Nhưng nếu bạn muốn linh hoạt toàn cầu hơn , chúng tôi có thể thêm một số #macros để thực hiện mặc định dễ thao tác.)
Kerrek SB

Vấn đề thực sự là, mặc dù tôi có thể dễ dàng sửa đổi print_container_helper để sử dụng các tham số cho các dấu phân cách tùy chỉnh, nhưng thực sự không có cách nào để chỉ định các dấu phân cách cho một thùng chứa bên trong (hoặc cặp) ngoài chuyên biệt mẫu phân cách. Đạt được điều đó sẽ rất phức tạp.
Sven

Tôi gần như quản lý để đạt được một giải pháp phân cách tùy chỉnh thuận tiện bằng cách sử dụng kiểu xóa. Nếu bạn đã có một lớp phân cách MyDels, thì tôi có thể nói std::cout << CustomPrinter<MyDels>(x);. Điều tôi không thể làm lúc này là nói std::cout << CustomDelims<"{", ":", "}">(x);, bởi vì bạn không thể có const char *đối số mẫu. Quyết định làm cho các dấu phân cách thời gian biên dịch liên tục đặt ra một số hạn chế về tính dễ sử dụng ở đó, nhưng tôi nghĩ rằng nó cũng xứng đáng.
Kerrek SB

22

Điều này đã được chỉnh sửa một vài lần và chúng tôi đã quyết định gọi lớp chính bao bọc bộ sưu tập RangePrinter

Điều này sẽ tự động hoạt động với bất kỳ bộ sưu tập nào khi bạn đã viết toán tử một lần << quá tải, ngoại trừ việc bạn sẽ cần một bản đồ đặc biệt để in cặp và có thể muốn tùy chỉnh dấu phân cách ở đó.

Bạn cũng có thể có một chức năng "in" đặc biệt để sử dụng trên vật phẩm thay vì chỉ xuất nó trực tiếp. Một chút giống như thuật toán STL cho phép bạn vượt qua các vị từ tùy chỉnh. Với bản đồ, bạn sẽ sử dụng nó theo cách này, với một máy in tùy chỉnh cho cặp std ::.

Máy in "mặc định" của bạn sẽ chỉ xuất nó ra luồng.

Ok, hãy làm việc trên một máy in tùy chỉnh. Tôi sẽ thay đổi lớp bên ngoài của mình thành RangePrinter. Vì vậy, chúng tôi có 2 vòng lặp và một số dấu phân cách nhưng chưa tùy chỉnh cách in các mục thực tế.

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

Bây giờ theo mặc định, nó sẽ hoạt động cho các bản đồ miễn là các loại khóa và giá trị đều có thể in được và bạn có thể đặt máy in vật phẩm đặc biệt của riêng mình khi chúng không (như bạn có thể với bất kỳ loại nào khác) hoặc nếu bạn không muốn = là dấu phân cách.

Tôi đang chuyển chức năng tự do để tạo những thứ này đến cùng:

Một chức năng miễn phí (phiên bản lặp) sẽ trông giống như cái này và bạn thậm chí có thể có mặc định:

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

Sau đó, bạn có thể sử dụng nó cho std :: set bởi

 std::cout << outputFormatter( mySet );

Bạn cũng có thể viết phiên bản chức năng miễn phí có một máy in tùy chỉnh và phiên bản có hai vòng lặp. Trong mọi trường hợp, họ sẽ giải quyết các tham số mẫu cho bạn và bạn sẽ có thể chuyển chúng qua dưới dạng tạm thời.


Tôi hiểu rồi. Điều này tương tự với ý tưởng của Marcelo Cantos, phải không? Tôi sẽ cố gắng biến điều này thành một ví dụ hoạt động, cảm ơn bạn!
Kerrek SB

Tôi thấy giải pháp này sạch hơn nhiều so với Marcelo và nó mang lại sự linh hoạt tương tự. Tôi thích khía cạnh mà người ta phải bao bọc rõ ràng đầu ra thành một lời gọi hàm. Để thực sự tuyệt vời, bạn có thể thêm hỗ trợ để xuất trực tiếp một loạt các trình vòng lặp, để tôi có thể làm được std::cout << outputFormatter(beginOfRange, endOfRange);.
Bjorn Pollex

1
@CashCow: có một vấn đề với giải pháp này, nó dường như không hoạt động với các bộ sưu tập đệ quy (tức là bộ sưu tập các bộ sưu tập). std::pairlà ví dụ cơ bản nhất của "bộ sưu tập bên trong".
Matthieu M.

Tôi rất thích câu trả lời này, vì nó không có phụ thuộc và không cần biết về các container mà nó hỗ trợ. Chúng ta có thể tìm ra nếu nó có thể xử lý std::mapdễ dàng và nếu nó hoạt động cho các bộ sưu tập của bộ sưu tập? Mặc dù vậy, tôi rất muốn chấp nhận điều này như một câu trả lời. Tôi hy vọng Marcelo không phiền, giải pháp của anh ấy cũng hiệu quả.
Kerrek SB

@Matthieu M. Nó phụ thuộc vào cách bạn in bộ sưu tập bên trong. Nếu bạn chỉ sử dụng os << open << * iter << close thì bạn sẽ gặp vấn đề với nó, nhưng nếu bạn cho phép người dùng của bạn chuyển qua một máy in tùy chỉnh như tôi đã đề nghị thì bạn có thể in bất cứ thứ gì bạn thích.
CashCow

14

Đây là một thư viện làm việc, được trình bày dưới dạng một chương trình làm việc hoàn chỉnh, mà tôi vừa hack cùng nhau:

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

Nó hiện chỉ hoạt động với vectorset, nhưng có thể được thực hiện để làm việc với hầu hết các container, chỉ bằng cách mở rộng trên các IsContainerchuyên ngành. Tôi đã không nghĩ nhiều về việc liệu mã này là tối thiểu, nhưng tôi không thể nghĩ ngay đến bất cứ thứ gì tôi có thể loại bỏ là dư thừa.

EDIT: Chỉ để đá, tôi đã bao gồm một phiên bản xử lý các mảng. Tôi đã phải loại trừ mảng char để tránh sự mơ hồ hơn nữa; nó vẫn có thể gặp rắc rối với wchar_t[].


2
@Nawaz: Như tôi đã nói, đây chỉ là khởi đầu của một giải pháp. Bạn có thể hỗ trợ std::map<>bằng cách chuyên các nhà điều hành, hoặc bằng cách định nghĩa một operator<<cho std::pair<>.
Marcelo Cantos

Tuy nhiên, +1 để sử dụng Delimsmẫu lớp!
Nawaz

@MC: Ồ tốt. Điều này có vẻ rất hứa hẹn! (Nhân tiện, bạn cần loại trả về "std :: ostream &", ban đầu tôi đã quên điều đó.)
Kerrek SB

Hmm, tôi nhận được "quá tải mơ hồ" khi thử điều này trên std :: vector <int> và std :: set <std :: string> ...
Kerrek SB

Đúng, tôi hiện đang tìm cách ngăn chặn sự mơ hồ, nguyên nhân là do operator<<mẫu phù hợp với bất cứ thứ gì.
Marcelo Cantos

10

Bạn có thể định dạng các thùng chứa cũng như phạm vi và bộ dữ liệu bằng thư viện {fmt} . Ví dụ:

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

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

in

{1, 2, 3}

để stdout.

Tuyên bố miễn trừ trách nhiệm : Tôi là tác giả của {fmt}.


8

Mã được chứng minh là tiện dụng trong một số trường hợp bây giờ và tôi cảm thấy chi phí để có được tùy chỉnh vì việc sử dụng khá thấp. Vì vậy, tôi quyết định phát hành nó theo giấy phép MIT và cung cấp kho lưu trữ GitHub nơi tiêu đề và tệp ví dụ nhỏ có thể được tải xuống.

http://djmuw.github.io/prettycc

0. Lời nói đầu và từ ngữ

Một "trang trí" theo câu trả lời này là một tập hợp chuỗi tiền tố, chuỗi phân cách và chuỗi hậu tố. Trong đó chuỗi tiền tố được chèn vào luồng trước và chuỗi hậu tố sau các giá trị của vùng chứa (xem 2. Vùng chứa mục tiêu). Chuỗi phân cách được chèn giữa các giá trị của vùng chứa tương ứng.

Lưu ý: Trên thực tế, câu trả lời này không giải quyết câu hỏi đến 100% vì trang trí không được biên dịch đúng thời gian bởi vì kiểm tra thời gian chạy được yêu cầu để kiểm tra xem một trang trí tùy chỉnh đã được áp dụng cho luồng hiện tại chưa. Tuy nhiên, tôi nghĩ rằng nó có một số tính năng tốt.

Lưu ý2: Có thể có một số lỗi nhỏ do chưa được thử nghiệm tốt.

1. Ý tưởng chung / cách sử dụng

Không cần thêm mã để sử dụng

Nó được giữ dễ dàng như

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

Dễ dàng tùy chỉnh ...

... đối với một đối tượng luồng cụ thể

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

hoặc đối với tất cả các luồng:

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

Mô tả thô

  • Mã này bao gồm một mẫu lớp cung cấp một trang trí mặc định cho bất kỳ loại nào
  • có thể được chuyên dùng để thay đổi trang trí mặc định cho (a) loại nhất định và đó là
  • sử dụng bộ nhớ riêng được cung cấp bằng ios_basecách sử dụng xalloc/ pwordđể lưu con trỏ vào một pretty::decorđối tượng cụ thể trang trí một loại nhất định trên một luồng nhất định.

Nếu không có pretty::decor<T>đối tượng cho luồng này đã được thiết lập rõ ràng pretty::defaulted<T, charT, chartraitT>::decoration()được gọi để có được trang trí mặc định cho loại đã cho. Các lớp học pretty::defaultedlà chuyên ngành để tùy chỉnh trang trí mặc định.

2. Đối tượng / container mục tiêu

Các đối tượng mục tiêu objcho 'trang trí đẹp' của mã này là các đối tượng có một trong hai

  • quá tải std::beginstd::endđược xác định (bao gồm mảng C-Style),
  • begin(obj)end(obj)có sẵn thông qua ADL,
  • thuộc loại std::tuple
  • hoặc loại std::pair.

Mã này bao gồm một đặc điểm để xác định các lớp có tính năng phạm vi ( begin/ end). (Tuy nhiên, không có kiểm tra bao gồm, dù begin(obj) == end(obj)là biểu thức hợp lệ.)

Mã này cung cấp operator<<s trong không gian tên toàn cục chỉ áp dụng cho các lớp không có phiên bản chuyên biệt hơn operator<<có sẵn. Do đó, ví dụ std::stringkhông được in bằng toán tử trong mã này mặc dù có hợp lệ begin/ endcặp.

3. Sử dụng và tùy biến

Trang trí có thể được áp đặt riêng cho mọi loại (trừ tuples khác nhau ) và luồng (không phải loại luồng!). (Tức là std::vector<int>có thể có các trang trí khác nhau cho các đối tượng luồng khác nhau.)

A) Trang trí mặc định

Tiền tố mặc định là ""(không có gì) như là tiền tố mặc định, trong khi dấu phân cách mặc định là ", "(dấu phẩy + dấu cách).

B) Trang trí mặc định tùy chỉnh của một loại bằng cách chuyên biệt pretty::defaultedmẫu lớp

Hàm struct defaultedcó một hàm thành viên tĩnh decoration()trả về một decorđối tượng bao gồm các giá trị mặc định cho kiểu đã cho.

Ví dụ sử dụng một mảng:

Tùy chỉnh in mảng mặc định:

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

In một mảng 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)

Sử dụng PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)macro cho charluồng

Macro mở rộng thành

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

cho phép chuyên môn hóa một phần ở trên được viết lại thành

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

hoặc chèn một chuyên ngành đầy đủ như

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

Một macro khác cho wchar_tcác luồng được bao gồm : PRETTY_DEFAULT_WDECORATION.

C) Giả trang trí trên suối

Hàm pretty::decorationđược sử dụng để áp đặt một trang trí trên một luồng nhất định. Có quá tải lấy một - một đối số chuỗi là dấu phân cách (chấp nhận tiền tố và hậu tố từ lớp mặc định) - hoặc ba đối số chuỗi lắp ráp trang trí hoàn chỉnh

Hoàn thành trang trí cho loại và luồng nhất định

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

Tùy chỉnh dấu phân cách cho luồng đã cho

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. Xử lý đặc biệt std::tuple

Thay vì cho phép chuyên môn hóa cho mọi loại tuple có thể, mã này áp dụng mọi trang trí có sẵn cho std::tuple<void*>tất cả các loại std::tuple<...>s.

5. Xóa trang trí tùy chỉnh khỏi luồng

Để quay lại trang trí mặc định cho một kiểu pretty::clearhàm nhất định, hãy sử dụng mẫu hàm trên luồng s.

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

5. Ví dụ khác

In "giống như ma trận" với dấu phân cách dòng mới

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;

Bản in

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

Xem nó trên ideone / KKUebZ

6. Mã

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

4

Tôi sẽ thêm một câu trả lời khác ở đây, bởi vì tôi đã đưa ra một cách tiếp cận khác với cách tiếp cận trước đây của tôi, và đó là sử dụng các khía cạnh bản địa.

Những điều cơ bản là ở đây

Về cơ bản những gì bạn làm là:

  1. Tạo một lớp có nguồn gốc từ std::locale::facet. Nhược điểm nhỏ là bạn sẽ cần một đơn vị biên dịch ở đâu đó để giữ id của nó. Hãy gọi nó là MyPrettyVectorPrinter. Bạn có thể đặt cho nó một cái tên hay hơn và cũng tạo một cái cho cặp và bản đồ.
  2. Trong chức năng truyền phát của bạn, bạn kiểm tra std::has_facet< MyPrettyVectorPrinter >
  3. Nếu điều đó trả về đúng, giải nén nó với std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. Các đối tượng khía cạnh của bạn sẽ có các giá trị cho các dấu phân cách và bạn có thể đọc chúng. Nếu khía cạnh không được tìm thấy, hàm in của bạn ( operator<<) sẽ cung cấp các mặc định. Lưu ý bạn có thể làm điều tương tự để đọc một vectơ.

Tôi thích phương pháp này vì bạn có thể sử dụng một bản in mặc định trong khi vẫn có thể sử dụng ghi đè tùy chỉnh.

Nhược điểm là cần một thư viện cho khía cạnh của bạn nếu được sử dụng trong nhiều dự án (vì vậy không thể chỉ là tiêu đề) và thực tế là bạn cần cẩn thận về chi phí tạo đối tượng địa phương mới.

Tôi đã viết đây là một giải pháp mới thay vì sửa đổi một giải pháp khác vì tôi tin rằng cả hai cách tiếp cận đều có thể đúng và bạn chọn lựa.


Hãy để tôi nói thẳng điều này: Với cách tiếp cận này, tôi có cần chủ động đưa danh sách trắng từng loại container mà tôi muốn sử dụng không?
Kerrek SB

Thực sự người ta không nên mở rộng tiêu chuẩn khác với các loại riêng, nhưng bạn viết quá tải toán tử << cho từng loại container (vectơ, bản đồ, danh sách, deque) cộng với cặp mà bạn muốn có thể in. Tất nhiên một số có thể chia sẻ một khía cạnh (ví dụ: bạn có thể muốn in danh sách, vectơ và deque tất cả giống nhau). Bạn cung cấp phương thức in "mặc định" nhưng cho phép người dùng tạo một khía cạnh và ngôn ngữ và in trước khi in. Một chút giống như cách boost in date_time của họ. Người ta cũng có thể tải khía cạnh của họ lên miền địa phương để in theo cách đó theo mặc định.
CashCow

2

Mục tiêu ở đây là sử dụng ADL để thực hiện tùy chỉnh cách chúng tôi in đẹp.

Bạn chuyển vào một thẻ định dạng và ghi đè 4 hàm (trước, sau, giữa và xuống) trong không gian tên của thẻ. Điều này thay đổi cách trình định dạng in 'trang sức' khi lặp qua các thùng chứa.

Một trình định dạng mặc định {(a->b),(c->d)}dành cho bản đồ, (a,b,c)cho tupleoids, "hello"cho chuỗi, [x,y,z]cho mọi thứ khác được bao gồm.

Nó nên "chỉ hoạt động" với các loại lặp của bên thứ 3 (và coi chúng như "mọi thứ khác").

Nếu bạn muốn trang trí tùy chỉnh cho các lần lặp bên thứ 3 của mình, chỉ cần tạo thẻ của riêng bạn. Sẽ mất một chút công việc để xử lý gốc bản đồ (bạn cần quá tải pretty_print_descend( your_tagđể quay lại pretty_print::decorator::map_magic_tag<your_tag>). Có lẽ có một cách sạch hơn để làm điều này, không chắc chắn.

Một thư viện nhỏ để phát hiện khả năng lặp lại và 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>{}> {};

Một thư viện cho phép chúng ta truy cập nội dung của một đối tượng kiểu lặp hoặc tuple:

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

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

Một thư viện in đẹp:

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

Mã kiểm tra:

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

ví dụ sống

Điều này không sử dụng các tính năng của C ++ 14 (một số _tbí danh và auto&&lambdas), nhưng không có tính năng nào là thiết yếu.


@KerrekSB phiên bản làm việc, với một số thay đổi. Phần lớn các mã này là chung "lần tuples / iterables", và ưa thích định dạng (bao gồm ->trong pairs các maps) vào thời điểm này. Cốt lõi của thư viện in đẹp là tốt và nhỏ, đó là tốt đẹp. Tôi đã cố gắng làm cho nó dễ dàng mở rộng, không chắc tôi đã thành công.
Yakk - Adam Nevraumont

1

Giải pháp của tôi là simple.h , mà là một phần của scc gói. Tất cả các thùng chứa std, bản đồ, bộ, mảng c đều có thể in được.


Hấp dẫn. Tôi thích cách tiếp cận mẫu của mẫu cho các thùng chứa, nhưng nó có hoạt động đối với các thùng chứa tùy chỉnh và thùng chứa STL với các biến vị ngữ hoặc phân bổ không chuẩn không? (Tôi đã làm một cái gì đó tương tự cho một nỗ lực để thực hiện một bimap trong C ++ 0x dùng mẫu có sẵn variadic.) Ngoài ra, bạn dường như không sử dụng lặp quát cho thói quen in ấn của bạn; Tại sao việc sử dụng rõ ràng của một truy cập i?
Kerrek SB

Container với các vị từ không chuẩn là gì? Container tùy chỉnh phù hợp với chữ ký sẽ được in. Phân bổ không chuẩn không được hỗ trợ ngay bây giờ, nhưng nó rất dễ sửa. Tôi không cần điều này ngay bây giờ.
Leonid Volnitsky

Không có lý do chính đáng để sử dụng chỉ mục thay vì các vòng lặp. Lý do lịch sử. Sẽ sửa nó khi tôi có thời gian.
Leonid Volnitsky

Bởi "container với các biến vị ngữ không chuẩn" Tôi có nghĩa là một cái gì đó giống như std::setvới một bộ so sánh tùy chỉnh hoặc unmapered_map với một đẳng thức tùy chỉnh. Nó rất quan trọng để hỗ trợ các công trình đó.
Kerrek SB

1

Đến từ một trong những BoostCon đầu tiên (bây giờ được gọi là CppCon), tôi và hai người khác đã làm việc trên một thư viện để làm việc này. Điểm gắn bó chính là cần phải mở rộng không gian tên std. Điều đó hóa ra là không có đối với một thư viện boost.

Thật không may, các liên kết đến mã không còn hoạt động, nhưng bạn có thể tìm thấy một số mẩu tin thú vị trong các cuộc thảo luận (ít nhất là các liên kết không nói về cái gì để đặt tên cho nó!)

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


0

Đây là phiên bản thực hiện của tôi được thực hiện vào năm 2016

Mọi thứ trong một tiêu đề, vì vậy thật dễ dàng để sử dụng https://github.com/skident/eos/blob/master/include/eos/io/print.hpp

/*! \file       print.hpp
 *  \brief      Useful functions for work with STL containers. 
 *          
 *  Now it supports generic print for STL containers like: [elem1, elem2, elem3]
 *  Supported STL conrainers: vector, deque, list, set multiset, unordered_set,
 *  map, multimap, unordered_map, array
 *
 *  \author     Skident
 *  \date       02.09.2016
 *  \copyright  Skident Inc.
 */

#pragma once

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


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

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


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

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


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


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



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

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

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

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


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


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


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



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


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


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


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


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

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

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

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

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

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

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



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



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

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

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

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

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

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

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

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

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

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

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

    ///< specialization for unordered_map
    UMAP_TEMPLATE
    std::ostream& operator<<(std::ostream& os, const Container<Key, Value, Hash, Pred, Alloc>& collection)
    {
        PROCESS_KEY_VALUE_COLLECTION(os, collection)
        return os;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.