พริตตี้คอนเทนเนอร์ C ++ STL แบบสวย


389

โปรดรับทราบการปรับปรุงในตอนท้ายของโพสต์นี้

อัปเดต: ฉันได้สร้างโครงการสาธารณะบน GitHubสำหรับห้องสมุดนี้แล้ว!


ผมอยากจะมีแม่แบบเดียวที่และทุกครั้งจะดูแลสวยพิมพ์ภาชนะ STL operator<<ทั้งหมดผ่าน ในรหัสหลอกฉันกำลังมองหาสิ่งนี้:

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

ตอนนี้ฉันเห็นเทมเพลตเวทย์มนตร์มากมายที่นี่เพื่อฉันไม่เคยคิดว่าจะเป็นไปได้ดังนั้นฉันจึงสงสัยว่าใครจะแนะนำสิ่งที่จะตรงกับที่เก็บทั้งหมด C. อาจมีบางอย่างที่มีลักษณะเฉพาะที่สามารถคิดได้ว่ามีตัว iterator ที่จำเป็น ?

ขอบคุณมาก!


อัปเดต (และโซลูชัน)

หลังจากพูดถึงปัญหานี้อีกครั้งในช่อง 9ฉันได้รับคำตอบที่ยอดเยี่ยมจาก Sven Groot ซึ่งเมื่อรวมกับลักษณะของ SFINAE แล้วดูเหมือนจะแก้ปัญหาได้อย่างสมบูรณ์แบบ ตัวคั่นอาจเป็นแบบเฉพาะเจาะจงตัวอย่างพิเศษสำหรับ std :: set รวมอยู่ด้วยรวมถึงตัวอย่างของการใช้ตัวคั่นแบบกำหนดเอง

ผู้ช่วย "wrap_array ()" สามารถใช้ในการพิมพ์อาร์เรย์ C ดิบ อัปเดต: มีคู่และทูเปิลสำหรับการพิมพ์; ตัวคั่นเริ่มต้นคือเครื่องหมายวงเล็บแบบกลม

คุณลักษณะการเปิดใช้งานหากต้องการประเภท C ++ 0x แต่ด้วยการปรับเปลี่ยนบางอย่างมันควรจะเป็นไปได้ที่จะทำให้รุ่น C ++ 98 นี้ Tuples ต้องการเทมเพลต Variadic ดังนั้น C ++ 0x

ฉันขอให้ Sven โพสต์วิธีแก้ปัญหาที่นี่เพื่อให้ฉันสามารถยอมรับได้ แต่ในระหว่างนี้ฉันต้องการโพสต์รหัสด้วยตนเองเพื่อการอ้างอิง ( อัปเดต: Sven ได้โพสต์รหัสของเขาด้านล่างซึ่งฉันได้รับคำตอบแล้วรหัสของฉันใช้ลักษณะของประเภทคอนเทนเนอร์ซึ่งทำงานให้ฉัน แต่อาจทำให้เกิดพฤติกรรมที่ไม่คาดคิดกับคลาสที่ไม่ใช่คอนเทนเนอร์ที่มีตัววนซ้ำ)

ส่วนหัว (prettyprint.h):

#ifndef H_PRETTY_PRINT
#define H_PRETTY_PRINT


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


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

namespace pretty_print
{

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

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


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

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


    // Holds the delimiter values for a specific character type

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


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

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


    // Default delimiters

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


    // Delimiters for set

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


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

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


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

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

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

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

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

                stream << *it;
            }

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

    private:
        const T & _container;
    };


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

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

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

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

    private:
        const T & t;
    };

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

} // namespace pretty_print


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


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

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


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

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

    // Prints a container to the stream using default delimiters

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

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

        stream << value.first;

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

        stream << value.second;

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

        return stream;
    }
} // namespace std

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

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

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

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

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

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

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


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

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

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

        return stream;
    }
} // namespace std


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

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

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

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

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


#endif

ตัวอย่างการใช้งาน:

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

#include "prettyprint.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

แนวคิดเพิ่มเติมสำหรับการปรับปรุง:

  • ดำเนินการส่งออกสำหรับในทางเดียวกันคือเรามีมันstd::tuple<...> std::pair<S,T> อัปเดต:ตอนนี้เป็นคำถามแยกต่างหากสำหรับ SO ! อัปเดต:สิ่งนี้ได้ถูกนำไปใช้แล้วต้องขอบคุณ Xeo!
  • เพิ่มเนมสเปซเพื่อให้คลาสตัวช่วยไม่ตกในเนมสเปซส่วนกลาง เสร็จสิ้น
  • เพิ่มชื่อแทนเทมเพลต (หรือชื่ออื่นที่คล้ายกัน) เพื่อความสะดวกในการสร้างคลาสตัวคั่นแบบกำหนดเองหรือมาโครตัวประมวลผลล่วงหน้า

อัปเดตล่าสุด:

  • ฉันลบ iterator เอาท์พุทที่กำหนดเองเพื่อความง่ายในการวนซ้ำในฟังก์ชั่นการพิมพ์
  • รายละเอียดการใช้งานทั้งหมดอยู่ในpretty_printnamespace เฉพาะผู้ให้บริการสตรีมระดับโลกและpretty_print_arrayตัดคำเท่านั้นที่อยู่ในเนมสเปซส่วนกลาง
  • แก้ไขการกำหนดเนมสเปซเพื่อให้operator<<ถูกต้องstdแล้ว

หมายเหตุ:

  • การลบตัววนซ้ำเอาต์พุตหมายความว่าไม่มีวิธีใช้ std::copy()ในการพิมพ์สวย ฉันอาจคืนสถานะตัววนซ้ำที่สวยได้ถ้านี่เป็นคุณสมบัติที่ต้องการ แต่โค้ดของ Sven ด้านล่างมีการใช้งาน
  • มันเป็นการตัดสินใจออกแบบอย่างมีสติเพื่อทำให้ค่าคงที่ของเวลาคอมไพล์แทนค่าคงที่ของวัตถุ ซึ่งหมายความว่าคุณไม่สามารถจัดหาตัวคั่นแบบไดนามิกขณะรันไทม์ แต่ก็หมายความว่าไม่มีค่าใช้จ่ายที่ไม่จำเป็น การกำหนดค่าตัวคั่นเชิงวัตถุได้รับการเสนอโดย Dennis Zickefoose ในข้อคิดเห็นของรหัส Sven ด้านล่าง หากต้องการสิ่งนี้สามารถนำไปใช้เป็นคุณสมบัติเสริมได้
  • ขณะนี้ยังไม่ชัดเจนว่าจะกำหนดตัวคั่นคอนเทนเนอร์ซ้อนกันได้อย่างไร
  • โปรดจำไว้ว่าจุดประสงค์ของห้องสมุดนี้คือเพื่ออำนวยความสะดวกในการพิมพ์ที่รวดเร็วการเข้ารหัสในส่วนของคุณ มันไม่ได้เป็นไลบรารีการจัดรูปแบบอเนกประสงค์ แต่เป็นเครื่องมือในการพัฒนาเพื่อลดความจำเป็นในการเขียนโค้ดแผ่นบอยเลอร์สำหรับการตรวจสอบคอนเทนเนอร์

ขอบคุณทุกคนที่มีส่วนร่วม!


หมายเหตุ:หากคุณกำลังมองหาวิธีที่รวดเร็วในการปรับใช้ตัวคั่นที่กำหนดเองนี่คือวิธีหนึ่งในการลบประเภท เราคิดว่าคุณได้สร้างคลาสตัวคั่นแล้วให้พูดMyDelดังนี้:

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

ตอนนี้เราต้องการเขียนstd::cout << MyPrinter(v) << std::endl;คอนเทนเนอร์vโดยใช้ตัวคั่นเหล่านั้น MyPrinterจะเป็นคลาสการลบประเภทเช่น:

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

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

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

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

รหัสของคุณใช้ไม่ได้ ไม่มีคำหลักเช่นคอนเทนเนอร์ C
the_drow

31
@the_drow: ดูเหมือนว่า OP จะรู้แล้ว พวกเขาเพียงแค่ระบุสิ่งที่พวกเขากำลังมองหา
Marcelo Cantos

ที่จริงฉันแค่ให้ตัวอย่างรหัสหลอก "คุณธรรม" (ฉันออกจากประเภทการส่งคืนฉันยังไม่ทราบ) เพื่อให้แน่ใจว่าฉันไม่รู้ด้วยซ้ำว่าวิธีที่ดีที่สุดในการทำให้ตัวคั่นเปลี่ยนได้
Kerrek SB

1
อีกทางเลือกหนึ่งคือการวางโอเปอเรเตอร์ในpretty_printเนมสเปซและจัดเตรียมเสื้อคลุมสำหรับผู้ใช้เมื่อพิมพ์ จากมุมมองของผู้ใช้: std::cout << pretty_print(v);(อาจมีชื่ออื่น) จากนั้นคุณสามารถจัดเตรียมโอเปอเรเตอร์ในเนมสเปซเดียวกันกับ wrapper และจากนั้นคุณสามารถขยายไปสู่การพิมพ์แบบสวย ๆ ที่คุณต้องการ นอกจากนี้คุณยังสามารถเพิ่มประสิทธิภาพของ wrapper ที่อนุญาตให้เลือกกำหนดตัวคั่นเพื่อใช้ภายในการโทรแต่ละครั้ง (แทนที่จะใช้ลักษณะที่บังคับให้มีตัวเลือกเดียวกันสำหรับแอปพลิเคชันทั้งหมด) \
David Rodríguez - dribeas

1
โปรดสร้างคำตอบ "อัปเดต" ของคุณให้เป็นคำตอบจริงแทนที่จะตอบคำถามที่มีมนุษยธรรม
einpoklum

คำตอบ:


82

โซลูชันนี้ได้รับแรงบันดาลใจจากโซลูชันของ Marcelo โดยมีการเปลี่ยนแปลงเล็กน้อย:

#include <iostream>
#include <iterator>
#include <type_traits>
#include <vector>
#include <algorithm>

// This works similar to ostream_iterator, but doesn't print a delimiter after the final item
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar> >
class pretty_ostream_iterator : public std::iterator<std::output_iterator_tag, void, void, void, void>
{
public:
    typedef TChar char_type;
    typedef TCharTraits traits_type;
    typedef std::basic_ostream<TChar, TCharTraits> ostream_type;

    pretty_ostream_iterator(ostream_type &stream, const char_type *delim = NULL)
        : _stream(&stream), _delim(delim), _insertDelim(false)
    {
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator=(const T &value)
    {
        if( _delim != NULL )
        {
            // Don't insert a delimiter if this is the first time the function is called
            if( _insertDelim )
                (*_stream) << _delim;
            else
                _insertDelim = true;
        }
        (*_stream) << value;
        return *this;
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator*()
    {
        return *this;
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator++()
    {
        return *this;
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator++(int)
    {
        return *this;
    }
private:
    ostream_type *_stream;
    const char_type *_delim;
    bool _insertDelim;
};

#if _MSC_VER >= 1400

// Declare pretty_ostream_iterator as checked
template<typename T, typename TChar, typename TCharTraits>
struct std::_Is_checked_helper<pretty_ostream_iterator<T, TChar, TCharTraits> > : public std::tr1::true_type
{
};

#endif // _MSC_VER >= 1400

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

// Basic is_container template; specialize to derive from std::true_type for all desired container types
template<typename T> struct is_container : public std::false_type { };

// Mark vector as a container
template<typename T, typename TAllocator> struct is_container<std::vector<T, TAllocator> > : public std::true_type { };

// Mark list as a container
template<typename T, typename TAllocator> struct is_container<std::list<T, TAllocator> > : public std::true_type { };

// Mark set as a container
template<typename T, typename TTraits, typename TAllocator> struct is_container<std::set<T, TTraits, TAllocator> > : public std::true_type { };

// Mark map as a container
template<typename TKey, typename TValue, typename TTraits, typename TAllocator> struct is_container<std::map<TKey, TValue, TTraits, TAllocator> > : public std::true_type { };

// Holds the delimiter values for a specific character type
template<typename TChar>
struct delimiters_values
{
    typedef TChar char_type;
    const TChar *prefix;
    const TChar *delimiter;
    const TChar *postfix;
};

// Defines the delimiter values for a specific container and character type
template<typename T, typename TChar>
struct delimiters
{
    static const delimiters_values<TChar> values; 
};

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

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

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

// Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type.
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar>, typename TDelimiters = delimiters<T, TChar> >
struct print_container_helper
{
    typedef TChar char_type;
    typedef TDelimiters delimiters_type;
    typedef std::basic_ostream<TChar, TCharTraits>& ostream_type;

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

    void operator()(ostream_type &stream) const
    {
        if( delimiters_type::values.prefix != NULL )
            stream << delimiters_type::values.prefix;
        std::copy(_container->begin(), _container->end(), pretty_ostream_iterator<typename T::value_type, TChar, TCharTraits>(stream, delimiters_type::values.delimiter));
        if( delimiters_type::values.postfix != NULL )
            stream << delimiters_type::values.postfix;
    }
private:
    const T *_container;
};

// Prints a print_container_helper to the specified stream.
template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const print_container_helper<T, TChar, TDelimiters> &helper)
{
    helper(stream);
    return stream;
}

// Prints a container to the stream using default delimiters
template<typename T, typename TChar, typename TCharTraits>
typename std::enable_if<is_container<T>::value, std::basic_ostream<TChar, TCharTraits>&>::type
    operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const T &container)
{
    stream << print_container_helper<T, TChar, TCharTraits>(container);
    return stream;
}

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

    stream << value.first;

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

    stream << value.second;

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

// Used by the sample below to generate some values
struct fibonacci
{
    fibonacci() : f1(0), f2(1) { }
    int operator()()
    {
        int r = f1 + f2;
        f1 = f2;
        f2 = r;
        return f1;
    }
private:
    int f1;
    int f2;
};

int main()
{
    std::vector<int> v;
    std::generate_n(std::back_inserter(v), 10, fibonacci());

    std::cout << v << std::endl;

    // Example of using pretty_ostream_iterator directly
    std::generate_n(pretty_ostream_iterator<int>(std::cout, ";"), 20, fibonacci());
    std::cout << std::endl;
}

เช่นเดียวกับรุ่นของ Marcelo มันใช้คุณสมบัติชนิด is_container ที่จะต้องมีเฉพาะสำหรับคอนเทนเนอร์ทั้งหมดที่ได้รับการสนับสนุน มันอาจจะเป็นไปได้ที่จะใช้ลักษณะการตรวจสอบvalue_type, const_iterator, begin()/ end()แต่ฉันไม่แน่ใจว่าผมอยากแนะนำว่าตั้งแต่มันอาจจะตรงกับสิ่งที่ตรงกับเกณฑ์เหล่านั้น std::basic_stringแต่ไม่ภาชนะจริงเช่น เช่นเดียวกับเวอร์ชันของ Marcelo มันใช้เทมเพลตที่สามารถระบุตัวคั่นเพื่อใช้

ความแตกต่างที่สำคัญคือฉันได้สร้างเวอร์ชันของฉันรอบ a pretty_ostream_iteratorซึ่งทำงานคล้ายกับstd::ostream_iteratorแต่ไม่ได้พิมพ์ตัวคั่นหลังจากรายการสุดท้าย การจัดรูปแบบคอนเทนเนอร์ทำได้โดยใช้print_container_helperซึ่งสามารถใช้โดยตรงเพื่อพิมพ์คอนเทนเนอร์โดยไม่มีลักษณะ is_container หรือเพื่อระบุประเภทของตัวคั่นอื่น

ฉันยังได้กำหนด is_container และตัวคั่นดังนั้นมันจะทำงานกับคอนเทนเนอร์ที่มีเพรดิเคตหรือตัวจัดสรรที่ไม่ได้มาตรฐานและสำหรับทั้งอักขระถ่านและ ตัวดำเนินการ << ฟังก์ชันนั้นถูกกำหนดให้ทำงานกับทั้ง char และ wchar_t

ในที่สุดฉันก็ใช้std::enable_ifซึ่งเป็นส่วนหนึ่งของ C ++ 0x และทำงานใน Visual C ++ 2010 และ g ++ 4.3 (ต้องการแฟล็ก -std = c ++ 0x) และใหม่กว่า วิธีนี้ไม่มีการพึ่งพา Boost


หากฉันกำลังอ่านสิทธิ์นี้เพื่อให้การพิมพ์แบบคู่เหมือนกับ<i, j>ในฟังก์ชั่นหนึ่งและอย่าง[i j]อื่นคุณต้องกำหนดประเภทใหม่ทั้งหมดด้วยสมาชิกแบบสแตติกจำนวนหนึ่งเพื่อส่งประเภทนั้นไปที่print_container_helper? ดูเหมือนซับซ้อนเกินไป ทำไมไม่ไปกับวัตถุจริงด้วยเขตข้อมูลที่คุณสามารถตั้งค่าเป็นกรณี ๆ ไปและความเชี่ยวชาญเฉพาะให้ค่าเริ่มต้นที่แตกต่างกันอย่างไร
Dennis Zickefoose

ดูด้วยวิธีนี้: ถ้ามีตัวคั่นหลายตัวที่คุณชอบเป็นการส่วนตัวคุณสามารถสร้างคลาสกับสมาชิกแบบสแตติกสองสามครั้งและทั้งหมดจากนั้นก็ใช้สิ่งเหล่านั้น แน่นอนว่าคุณพูดถูกว่าการใช้print_container_helperไม่ได้สง่างามเท่าที่operator<<ควร คุณสามารถเปลี่ยนแหล่งที่มาของหลักสูตรหรือเพียงแค่เพิ่มความเชี่ยวชาญที่ชัดเจนสำหรับภาชนะที่คุณชื่นชอบเช่นการและpair<int, int> pair<double, string>ในที่สุดมันเป็นเรื่องของการชั่งน้ำหนักพลังงานกับความสะดวกสบาย ข้อเสนอแนะสำหรับการปรับปรุงการต้อนรับ!
Kerrek SB

... และเพื่อติดตามว่าหากคุณต้องการพิมพ์สถานการณ์ชนิดข้อมูลเดียวกันในรูปแบบที่แตกต่างกันอยู่แล้วคุณอาจจะต้องเขียน wrapper ขนาดเล็กอย่างน้อยหนึ่งตัว นี่ไม่ใช่ไลบรารีการจัดรูปแบบที่สามารถกำหนดค่าได้สูง แต่เป็นไลบราแบบเริ่มต้นที่ไม่ต้องใช้ความพยายามซึ่งช่วยให้คุณสามารถพิมพ์คอนเทนเนอร์โดยไม่ต้องคิด ... (แต่ถ้าคุณต้องการความยืดหยุ่นระดับโลกมากขึ้นเราอาจเพิ่ม #macros ค่าเริ่มต้นง่ายต่อการจัดการ)
Kerrek SB

ปัญหาจริงคือแม้ว่าฉันสามารถแก้ไข print_container_helper เพื่อใช้พารามิเตอร์สำหรับตัวคั่นแบบกำหนดเองได้อย่างง่ายดาย แต่ก็ไม่มีวิธีใดที่จะระบุตัวคั่นสำหรับคอนเทนเนอร์ภายใน (หรือคู่) นอกเหนือจากเทมเพลตตัวคั่นที่เชี่ยวชาญ ความสำเร็จที่จะซับซ้อนมาก
Sven

ฉันเกือบจะจัดการเพื่อให้ได้โซลูชันตัวคั่นแบบกำหนดเองที่สะดวกโดยใช้การลบประเภท หากคุณมีคลาสตัวคั่นอยู่MyDelsแล้วฉันสามารถพูดstd::cout << CustomPrinter<MyDels>(x);ได้ สิ่งที่ฉันไม่สามารถทำได้ในขณะนี้คือการพูดstd::cout << CustomDelims<"{", ":", "}">(x);เพราะคุณไม่สามารถมีconst char *ข้อโต้แย้งแม่แบบ การตัดสินใจที่จะทำให้ตัวคั่นเวลาคงที่รวบรวมทำให้ข้อ จำกัด บางอย่างเกี่ยวกับความสะดวกในการใช้งานที่นั่น แต่ฉันคิดว่ามันคุ้มค่าดี
Kerrek SB

22

สิ่งนี้ถูกแก้ไขสองสามครั้งและเราตัดสินใจเรียกคลาสหลักที่ล้อมคอลเลกชัน RangePrinter

สิ่งนี้จะทำงานโดยอัตโนมัติกับคอลเล็กชันใด ๆ เมื่อคุณเขียนโอเปอเรเตอร์ << โอเวอร์โหลดหนึ่งครั้งยกเว้นว่าคุณจะต้องใช้อันพิเศษสำหรับแผนที่เพื่อพิมพ์คู่และอาจต้องการกำหนดตัวคั่นที่นั่น

คุณสามารถมีฟังก์ชั่น "พิมพ์" พิเศษเพื่อใช้กับรายการแทนที่จะส่งออกโดยตรง อัลกอริธึมแบบ STL ช่วยให้คุณสามารถผ่านเพรดิเคตที่กำหนดเองได้ ด้วยแผนที่คุณจะใช้วิธีนี้พร้อมกับเครื่องพิมพ์ที่กำหนดเองสำหรับคู่ std ::

เครื่องพิมพ์ "เริ่มต้น" ของคุณเพิ่งจะส่งออกไปยังกระแส

ตกลงมาทำงานกับเครื่องพิมพ์ที่กำหนดเอง ฉันจะเปลี่ยนคลาสภายนอกของฉันเป็น RangePrinter ดังนั้นเราจึงมีตัววนซ้ำ 2 ตัวและตัวคั่นบางตัว แต่ยังไม่ได้ปรับแต่งวิธีการพิมพ์รายการจริง

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

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

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

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

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

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

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

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

};


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

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

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

    printer( os, *begin );

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

ตอนนี้โดยค่าเริ่มต้นมันจะใช้งานได้กับแผนที่ตราบใดที่คีย์และประเภทค่านั้นสามารถพิมพ์ได้และคุณสามารถใส่เครื่องพิมพ์รายการพิเศษของคุณเองได้เมื่อไม่ได้ใช้ (คุณสามารถพิมพ์ได้) = เป็นตัวคั่น

ฉันกำลังย้ายฟังก์ชั่นอิสระเพื่อสร้างสิ่งเหล่านี้ให้จบ:

ฟังก์ชั่นฟรี (เวอร์ชั่นตัววนซ้ำ) จะมีลักษณะเช่นนี้และคุณอาจมีค่าเริ่มต้น:

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

คุณสามารถใช้สำหรับ std :: set by

 std::cout << outputFormatter( mySet );

คุณยังสามารถเขียนรุ่นฟังก์ชั่นอิสระที่ใช้เครื่องพิมพ์ที่กำหนดเองและรุ่นที่มีตัววนซ้ำสองตัว ไม่ว่าในกรณีใดพวกเขาจะแก้ไขพารามิเตอร์เทมเพลตสำหรับคุณและคุณจะสามารถส่งผ่านพารามิเตอร์เหล่านั้นเป็นเวลาชั่วคราว


ฉันเห็น. นี่คล้ายกับแนวคิดของ Marcelo Cantos ใช่ไหม ฉันจะพยายามทำให้สิ่งนี้กลายเป็นตัวอย่างการทำงานขอบคุณ!
Kerrek SB

ฉันพบว่าวิธีนี้สะอาดกว่าของ Marcelo มากและมีความยืดหยุ่นเหมือนกัน ฉันชอบมุมมองที่ต้องห่อเอาท์พุทอย่างชัดเจนในการเรียกใช้ฟังก์ชัน การจะมีความเย็นจริงๆคุณสามารถเพิ่มการสนับสนุนสำหรับการแสดงผลช่วงของ iterators std::cout << outputFormatter(beginOfRange, endOfRange);โดยตรงเพื่อที่ฉันสามารถทำได้
Björn Pollex

1
@CashCow: มีปัญหาหนึ่งเกี่ยวกับโซลูชันนี้ดูเหมือนจะไม่ทำงานกับคอลเลกชันที่เรียกซ้ำ (เช่นคอลเล็กชันของคอลเลกชัน) std::pairเป็นตัวอย่างพื้นฐานที่สุดของ "inner collection"
Matthieu M.

ฉันชอบคำตอบนี้มากเนื่องจากไม่มีการอ้างอิงและไม่จำเป็นต้องรู้เกี่ยวกับคอนเทนเนอร์ที่รองรับ เราสามารถคิดออกว่ามันสามารถจัดการกับstd::maps ได้อย่างง่ายดายและถ้ามันทำงานกับคอลเลกชันของคอลเลกชัน? ฉันอยากจะยอมรับคำตอบนี้ ฉันหวังว่า Marcelo ไม่สนใจวิธีแก้ปัญหาของเขาก็ใช้ได้เช่นกัน
Kerrek SB

@Matthieu M. มันขึ้นอยู่กับวิธีที่คุณพิมพ์คอลเลกชันภายใน หากคุณเพิ่งใช้ OS << open << * iter << close คุณจะมีปัญหากับมัน แต่ถ้าคุณอนุญาตให้ผู้ใช้ของคุณผ่านเครื่องพิมพ์ที่กำหนดเองได้ตามที่ฉันแนะนำให้คุณพิมพ์สิ่งที่คุณต้องการ
CashCow

14

นี่คือไลบรารีการทำงานที่นำเสนอเป็นโปรแกรมการทำงานที่สมบูรณ์ที่ฉันเพิ่งแฮกเข้าด้วยกัน:

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

#include <boost/utility/enable_if.hpp>

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

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

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

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

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

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

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

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

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

ปัจจุบันนี้ใช้งานได้เฉพาะกับvectorและsetแต่สามารถทำงานกับตู้คอนเทนเนอร์ส่วนใหญ่ได้เพียงแค่ขยายIsContainerความเชี่ยวชาญเฉพาะด้าน ฉันไม่ได้คิดมากเกี่ยวกับว่ารหัสนี้มีน้อย แต่ฉันไม่สามารถคิดได้ทันทีว่าอะไรที่ฉันสามารถตัดออกเป็นซ้ำซ้อน

แก้ไข:สำหรับเตะฉันรวมรุ่นที่จัดการกับอาร์เรย์ ฉันต้องไม่รวมอาร์เรย์ถ่านเพื่อหลีกเลี่ยงความคลุมเครือเพิ่มเติม; wchar_t[]ก็ยังอาจได้รับเป็นปัญหากับ


2
@ นาวาซ: ดังที่ฉันพูดไปนี่เป็นเพียงจุดเริ่มต้นของการแก้ปัญหา คุณสามารถสนับสนุนstd::map<>ทั้งโดยผู้ประกอบการที่มีความเชี่ยวชาญหรือโดยการกำหนดสำหรับoperator<< std::pair<>
Marcelo Cantos

อย่างไรก็ตาม +1 สำหรับการใช้Delimsแม่แบบคลาส!
Nawaz

@MC: โอ้ดี นี่มันดูมีแนวโน้มมาก! (โดยวิธีการที่คุณต้องกลับประเภท "std :: ostream &" ฉันลืมไปแล้วในตอนแรก)
Kerrek SB

อืมฉันได้ "overload ที่คลุมเครือ" เมื่อลองใช้ std :: vector <int> และ std :: set <std :: string> ...
Kerrek SB

ใช่ฉันกำลังค้นหาวิธีการป้องกันความกำกวมซึ่งเกิดจากความจริงที่ว่าoperator<<แม่แบบที่ตรงกับอะไรก็ได้
Marcelo Cantos

10

คุณสามารถจัดรูปแบบภาชนะเช่นเดียวกับช่วงและ tuples ใช้ห้องสมุด {} fmt ตัวอย่างเช่น:

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

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

พิมพ์

{1, 2, 3}

stdoutไปยัง

ข้อจำกัดความรับผิดชอบ : ฉันเป็นผู้เขียน {fmt}


8

ตอนนี้รหัสพิสูจน์แล้วว่ามีประโยชน์หลายครั้งและฉันรู้สึกว่าค่าใช้จ่ายในการปรับแต่งเนื่องจากการใช้งานค่อนข้างต่ำ ดังนั้นฉันตัดสินใจที่จะปล่อยมันภายใต้ใบอนุญาตMITและให้พื้นที่เก็บข้อมูล GitHub ที่ส่วนหัวและไฟล์ตัวอย่างขนาดเล็กสามารถดาวน์โหลดได้

http://djmuw.github.io/prettycc

0. คำนำและถ้อยคำ

A 'การตกแต่ง'ในแง่ของคำตอบนี้เป็นชุดของคำนำหน้าสตริงคั่นสตริงและ postfix สตริง โดยที่แทรกคำนำหน้าสตริงลงในสตรีมก่อนหน้าและสตริงคำนำหน้าหลังค่าของภาชนะ (ดูที่ 2 คอนเทนเนอร์เป้าหมาย) สตริงตัวคั่นถูกแทรกระหว่างค่าของคอนเทนเนอร์ที่เกี่ยวข้อง

หมายเหตุ: จริงๆแล้วคำตอบนี้ไม่ได้ตอบคำถาม 100% เนื่องจากการตกแต่งไม่ได้รวบรวมเวลาอย่างคงที่เนื่องจากการตรวจสอบรันไทม์จำเป็นต้องตรวจสอบว่าการตกแต่งที่กำหนดเองได้ถูกนำไปใช้กับกระแสปัจจุบัน อย่างไรก็ตามฉันคิดว่ามันมีคุณสมบัติที่เหมาะสม

Note2: อาจมีข้อผิดพลาดเล็กน้อยเนื่องจากยังไม่ผ่านการทดสอบ

1. ความคิดทั่วไป / การใช้งาน

ต้องใช้รหัสเพิ่มเติมเป็นศูนย์สำหรับการใช้งาน

มันจะถูกเก็บไว้เป็นเรื่องง่ายเหมือน

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

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

ปรับแต่งง่าย ...

... ด้วยความเคารพต่อวัตถุสตรีมที่เฉพาะเจาะจง

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

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

หรือด้วยความเคารพต่อสตรีมทั้งหมด:

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

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

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

คำอธิบายหยาบ

  • รหัสรวมถึงแม่แบบชั้นเรียนที่ให้การตกแต่งเริ่มต้นสำหรับทุกประเภท
  • ซึ่งสามารถเปลี่ยนการตกแต่งเริ่มต้นสำหรับ (a) บางประเภทและเป็น
  • การใช้ที่เก็บข้อมูลส่วนตัวที่มีให้โดยios_baseใช้xalloc/ pwordเพื่อบันทึกตัวชี้ไปยังpretty::decorวัตถุที่ตกแต่งประเภทเฉพาะในสตรีมที่แน่นอน

หากไม่มีpretty::decor<T>การตั้งค่าวัตถุสำหรับสตรีมนี้อย่างชัดเจนpretty::defaulted<T, charT, chartraitT>::decoration()จะได้รับการตกแต่งเริ่มต้นสำหรับประเภทที่กำหนด ชั้นpretty::defaultedจะต้องมีความเชี่ยวชาญในการปรับแต่งตกแต่งเริ่มต้น

2. วัตถุเป้าหมาย / ภาชนะ

วัตถุเป้าหมายobjสำหรับ'การตกแต่งที่สวยงาม'ของรหัสนี้เป็นวัตถุที่มี

  • เกินพิกัดstd::beginและstd::endกำหนดไว้ (รวมถึงอาร์เรย์ C-Style)
  • มีbegin(obj)และend(obj)ให้บริการผ่าน ADL
  • เป็นประเภท std::tuple
  • std::pairหรือประเภท

รหัสนี้มีคุณลักษณะสำหรับการระบุคลาสที่มีคุณสมบัติช่วง ( begin/ end) (ไม่มีเช็ครวมอยู่ด้วยไม่ว่าจะเป็นbegin(obj) == end(obj)จะเป็นนิพจน์ที่ถูกต้องก็ตาม)

รหัสจัดเตรียมoperator<<s ในเนมสเปซส่วนกลางที่ใช้กับคลาสที่ไม่มีเวอร์ชันที่พิเศษกว่าoperator<<เท่านั้น ดังนั้นตัวอย่างstd::stringจะไม่ถูกพิมพ์โดยใช้โอเปอเรเตอร์ในรหัสนี้แม้ว่าจะมีbegin/endคู่

3. การใช้และการปรับแต่ง

การตกแต่งสามารถกำหนดแยกต่างหากสำหรับทุกประเภท (ยกเว้นtuples) และสตรีม (ไม่ใช่ประเภทสตรีม!) (เช่นstd::vector<int>สามารถตกแต่งที่แตกต่างกันสำหรับวัตถุกระแสที่แตกต่างกัน)

A) การตกแต่งเริ่มต้น

คำนำหน้าเริ่มต้นคือ""(ไม่มีอะไร) เช่นเดียวกับคำนำหน้าเริ่มต้นในขณะที่ตัวคั่นเริ่มต้นคือ", "(เครื่องหมายจุลภาค + ช่องว่าง)

B) การตกแต่งเริ่มต้นที่กำหนดเองของประเภทโดยเฉพาะpretty::defaultedแม่แบบชั้นเรียน

struct defaultedมีฟังก์ชั่นสมาชิกคงdecoration()กลับdecorวัตถุซึ่งรวมถึงค่าเริ่มต้นสำหรับประเภทที่กำหนด

ตัวอย่างการใช้อาร์เรย์:

ปรับแต่งการพิมพ์อาร์เรย์เริ่มต้น:

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

พิมพ์อาร์เรย์ 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)

การใช้PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)มาโครสำหรับcharสตรีม

แมโครจะขยายเป็น

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

เปิดใช้งานความเชี่ยวชาญเฉพาะบางส่วนข้างต้นเพื่อเขียนใหม่

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

หรือแทรกความเชี่ยวชาญแบบเต็มเช่น

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

แมโครอีกลำธารจะรวม:wchar_tPRETTY_DEFAULT_WDECORATION

C) กำหนดการตกแต่งบนสตรีม

ฟังก์ชั่นpretty::decorationจะใช้ในการกำหนดการตกแต่งในกระแสบางอย่าง มีการรับน้ำหนักมากเกินไป - อาร์กิวเมนต์สตริงหนึ่งตัวเป็นตัวคั่น (ใช้คำนำหน้าและ postfix จากคลาสที่ผิดนัด) - หรืออาร์กิวเมนต์สตริงสามตัวที่ประกอบกันเป็นของตกแต่งที่สมบูรณ์

การตกแต่งเสร็จสมบูรณ์สำหรับประเภทและสตรีมที่กำหนด

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

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

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

การกำหนดค่าตัวคั่นสำหรับสตรีมที่กำหนด

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

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

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

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

4. การจัดการพิเศษของ std::tuple

แทนที่จะอนุญาตให้มีความเชี่ยวชาญสำหรับประเภททูเปิลที่เป็นไปได้ทั้งหมดรหัสนี้ใช้การตกแต่งใด ๆ ที่มีให้สำหรับs std::tuple<void*>ทุกชนิดstd::tuple<...>

5. ลบการตกแต่งที่กำหนดเองออกจากสตรีม

ที่จะกลับไปตกแต่งที่ผิดนัดสำหรับชนิดให้ใช้แม่แบบฟังก์ชั่นในกระแสpretty::clears

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

5. ตัวอย่างเพิ่มเติม

การพิมพ์ "คล้ายเมทริกซ์" ด้วยตัวคั่นบรรทัดใหม่

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

พิมพ์

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

ดูได้ที่ideone / KKUebZ

6. รหัส

#ifndef pretty_print_0x57547_sa4884X_0_1_h_guard_
#define pretty_print_0x57547_sa4884X_0_1_h_guard_

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

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

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

namespace pretty
{

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

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

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

  namespace detail
  {

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

    template<class T> struct clearer {};

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

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

  }

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

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

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

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

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

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

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

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

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

}

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

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

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


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

#endif // pretty_print_0x57547_sa4884X_0_1_h_guard_

4

ฉันจะเพิ่มคำตอบอื่นที่นี่เพราะฉันได้มาด้วยวิธีการที่แตกต่างกันไปก่อนหน้านี้ของฉันและนั่นคือการใช้แง่มุมสถานที่

พื้นฐานอยู่ที่นี่

สิ่งสำคัญที่คุณทำคือ:

  1. สร้างคลาสที่มาจาก std::locale::facetสร้างคลาสที่เกิดขึ้นจากข้อเสียเล็กน้อยคือคุณจะต้องมีหน่วยรวบรวมที่ไหนสักแห่งเพื่อเก็บ id ของมัน ลองเรียกมันว่า MyPrettyVectorPrinter คุณอาจให้ชื่อที่ดีกว่านี้และสร้างชื่อสำหรับคู่และแผนที่
  2. ในฟังก์ชั่นสตรีมของคุณคุณตรวจสอบ std::has_facet< MyPrettyVectorPrinter >
  3. หากผลตอบแทนจริงให้แตกออกมาด้วย std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. วัตถุ facet ของคุณจะมีค่าสำหรับตัวคั่นและคุณสามารถอ่านได้ หากไม่พบแง่มุมฟังก์ชันการพิมพ์ของคุณ ( operator<<) จะให้ค่าเริ่มต้น หมายเหตุคุณสามารถทำสิ่งเดียวกันสำหรับการอ่านเวกเตอร์

ฉันชอบวิธีนี้เพราะคุณสามารถใช้การพิมพ์เริ่มต้นในขณะที่ยังสามารถใช้การแทนที่แบบกำหนดเองได้

ข้อเสียคือต้องการไลบรารีสำหรับแง่มุมของคุณหากใช้ในหลายโครงการ (ดังนั้นไม่เพียง แต่เป็นส่วนหัวเท่านั้น) และความจริงที่ว่าคุณต้องระวังเกี่ยวกับค่าใช้จ่ายในการสร้างวัตถุสถานที่ใหม่

ฉันได้เขียนสิ่งนี้เป็นวิธีการแก้ปัญหาใหม่มากกว่าที่จะแก้ไขอื่น ๆ ของฉันเพราะฉันเชื่อว่าทั้งสองวิธีสามารถถูกต้องและคุณเลือกของคุณ


ให้ฉันได้สิ่งนี้โดยตรง: ด้วยวิธีการนี้ฉันต้องกำหนดรายการที่ปลอดภัยแต่ละประเภทที่ฉันต้องการใช้หรือไม่
Kerrek SB

ดีจริง ๆ ไม่ควรขยาย std อื่นนอกเหนือจากชนิดของตัวเอง แต่คุณเขียน overload ของตัวดำเนินการ << สำหรับแต่ละชนิดคอนเทนเนอร์ (เวกเตอร์, แผนที่, รายการ, deque) บวกคู่ที่คุณต้องการพิมพ์ แน่นอนว่าบางคนอาจแบ่งปันแง่มุม (เช่นคุณอาจต้องการพิมพ์รายการเวกเตอร์และ deque เหมือนกันทั้งหมด) คุณให้วิธีการพิมพ์ "เริ่มต้น" แต่อนุญาตให้ผู้ใช้สร้าง facet และ locale และ imbue ก่อนทำการพิมพ์ วิธีเพิ่มการพิมพ์ date_time เล็กน้อย ท่านสามารถโหลด facet ของพวกเขาลงในสถานที่ทั่วโลกเพื่อพิมพ์แบบนั้นโดยค่าเริ่มต้น
CashCow

2

เป้าหมายที่นี่คือการใช้ ADL เพื่อปรับแต่งวิธีที่เราพิมพ์สวย

คุณส่งผ่านแท็กตัวจัดรูปแบบและแทนที่ 4 ฟังก์ชัน (ก่อนหลังระหว่างและลง) ในเนมสเปซของแท็ก สิ่งนี้จะเปลี่ยนวิธีที่ตัวจัดรูปแบบพิมพ์ 'เครื่องประดับ' เมื่อวนรอบคอนเทนเนอร์

ตัวจัดรูปแบบเริ่มต้นที่ใช้{(a->b),(c->d)}สำหรับแผนที่(a,b,c)สำหรับ tupleoids "hello"สำหรับสตริง[x,y,z]สำหรับทุกอย่างรวมอยู่ด้วย

มันควรจะ "เพิ่งทำงาน" กับประเภทที่สามารถทลายได้ของบุคคลที่สาม (และปฏิบัติต่อพวกเขาเช่น "ทุกอย่าง")

หากคุณต้องการเครื่องประดับที่กำหนดเองสำหรับ iterables บุคคลที่สามของคุณเพียงแค่สร้างแท็กของคุณเอง จะต้องใช้เวลาสักครู่ในการจัดการกับการสืบเชื้อแผนที่ (คุณต้องโอเวอร์โหลดpretty_print_descend( your_tagเพื่อส่งคืนpretty_print::decorator::map_magic_tag<your_tag>) อาจมีวิธีที่สะอาดกว่าในการทำเช่นนี้ไม่แน่ใจ

ห้องสมุดเล็ก ๆ สำหรับตรวจสอบความสามารถในการทำซ้ำและ 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>{}> {};

ไลบรารีที่ให้เราเยี่ยมชมเนื้อหาของวัตถุประเภท iterable หรือ tuple:

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

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

ห้องสมุดการพิมพ์ที่น่ารัก:

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

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

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

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

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

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

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

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

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

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

รหัสทดสอบ:

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

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

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

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

ตัวอย่างสด

สิ่งนี้ใช้คุณสมบัติ C ++ 14 ( _tนามแฝงบางตัวและauto&&lambdas) แต่ไม่มีสิ่งใดที่จำเป็น


@KerrekSB เวอร์ชันการทำงานพร้อมการเปลี่ยนแปลงบางอย่าง ส่วนใหญ่ของรหัสคือ "เข้าชม tuples / iterables" ทั่วไปและการจัดรูปแบบแฟนซี (รวมถึง->ภายในpairs ของmaps) ณ จุดนี้ แกนกลางของไลบรารีการพิมพ์ที่สวยงามนั้นดีและเล็กซึ่งก็ดี ฉันพยายามทำให้มันยืดออกได้ง่ายไม่แน่ใจว่าฉันทำสำเร็จหรือไม่
Yakk - Adam Nevraumont

1

วิธีการแก้ปัญหาของฉันคือsimple.hซึ่งเป็นส่วนหนึ่งของแพคเกจscc คอนเทนเนอร์ std, แผนที่, ชุด, c-arrays ทั้งหมดสามารถพิมพ์ได้


น่าสนใจ ฉันชอบวิธีเทมเพลตสำหรับเทมเพลตสำหรับคอนเทนเนอร์ แต่ทำงานกับคอนเทนเนอร์ที่กำหนดเองและคอนเทนเนอร์ STL ที่มีเพรดิเคตหรือตัวจัดสรรที่ไม่ได้มาตรฐานหรือไม่ (ฉันทำสิ่งที่คล้ายกันเพื่อพยายามใช้ bimap ใน C ++ 0xโดยใช้เท็มเพลตแบบแปรปรวน ) นอกจากนี้คุณดูเหมือนจะไม่ใช้ตัววนซ้ำทั่วไปสำหรับงานพิมพ์ของคุณ ทำไมการใช้ตัวนับอย่างชัดเจนi?
Kerrek SB

คอนเทนเนอร์ที่มีเพรดิเคตที่ไม่ได้มาตรฐานคืออะไร ภาชนะที่กำหนดเองซึ่งลายเซ็นที่ตรงกันจะถูกพิมพ์ ไม่รองรับตัวจัดสรรที่ไม่ได้มาตรฐาน แต่มันง่ายต่อการแก้ไข ฉันไม่ต้องการสิ่งนี้ในตอนนี้
Leonid Volnitsky

ไม่มีเหตุผลที่ดีที่จะใช้ดัชนีแทนตัววนซ้ำ เหตุผลทางประวัติศาสตร์ จะแก้ไขเมื่อฉันมีเวลา
Leonid Volnitsky

โดย "คอนเทนเนอร์ที่มีเพรดิเคตที่ไม่ได้มาตรฐาน" ฉันหมายถึงสิ่งที่คล้ายstd::setกับที่มีตัวเปรียบเทียบที่กำหนดเองหรือ unordered_map ที่มีความเท่าเทียมกันที่กำหนดเอง มันสำคัญมากที่จะสนับสนุนสิ่งปลูกสร้างเหล่านั้น
Kerrek SB

1

ออกมาจากหนึ่งใน BoostCon แรก (ปัจจุบันเรียกว่า CppCon) ฉันและอีกสองคนทำงานในห้องสมุดเพื่อทำสิ่งนี้ จุดผสานหลักนั้นต้องการขยาย namespace std ซึ่งกลายเป็นสิ่งที่ไม่ต้องทำสำหรับห้องสมุดเพิ่ม

น่าเสียดายที่ลิงก์ไปยังโค้ดใช้งานไม่ได้อีกต่อไป แต่คุณอาจพบเกร็ดเล็กเกร็ดน้อยที่น่าสนใจในการสนทนา (อย่างน้อยพวกที่ไม่ได้พูดถึงสิ่งที่จะตั้งชื่อ!

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


0

นี่เป็นการนำเวอร์ชันของฉันไปใช้ในปี 2559

ทุกอย่างในส่วนหัวเดียวดังนั้นจึงง่ายต่อการใช้ 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;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.