c++ - सुंदर प्रिंट सी++ एसटीएल कंटेनर




templates c++11 (5)

इसे कुछ बार संपादित किया गया है, और हमने मुख्य श्रेणी को कॉल करने का निर्णय लिया है जो संग्रह रेंज प्रिंटर को लपेटता है

एक बार जब आप एक बार ऑपरेटर << अधिभार लिखते हैं, तो यह किसी भी संग्रह के साथ स्वचालित रूप से काम करना चाहिए, सिवाय इसके कि आपको जोड़ों को मुद्रित करने के लिए नक्शे के लिए एक विशेष आवश्यकता होगी, और वहां डिलीमीटर को कस्टमाइज़ करना चाहेंगे।

आप इसे सीधे आउटपुट करने के बजाय आइटम पर उपयोग करने के लिए एक विशेष "प्रिंट" फ़ंक्शन भी प्राप्त कर सकते हैं। एसटीएल एल्गोरिदम की तरह थोड़ा आपको कस्टम भविष्यवाणियों में पारित करने की अनुमति देता है। मानचित्र के साथ आप std :: जोड़ी के लिए कस्टम प्रिंटर के साथ इस तरह इसका उपयोग करेंगे।

आपका "डिफ़ॉल्ट" प्रिंटर बस इसे स्ट्रीम में आउटपुट करेगा।

ठीक है, चलो एक कस्टम प्रिंटर पर काम करते हैं। मैं अपनी बाहरी कक्षा को रेंज प्रिंटर में बदल दूंगा। तो हमारे पास 2 इटरेटर और कुछ डिलीमीटर हैं लेकिन वास्तविक वस्तुओं को प्रिंट करने के तरीके को अनुकूलित नहीं किया है।

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

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

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

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

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

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

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

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

};


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

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

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

    printer( os, *begin );

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

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

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

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

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

You could then use it for std::set by

 std::cout << outputFormatter( mySet );

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

कृपया इस पोस्ट के अंत में अपडेट का ध्यान रखें।

अद्यतन: मैंने इस पुस्तकालय के लिए गिटहब पर एक सार्वजनिक परियोजना बनाई है!

मैं एक ऐसा टेम्पलेट रखना चाहता हूं जो एक बार और सभी के लिए operator<< माध्यम से सभी एसटीएल कंटेनरों को सुंदर प्रिंटिंग का ख्याल रखता है 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;
}

अब मैंने एसओ पर बहुत सारे टेम्पलेट जादू को देखा है, जिसे मैंने कभी सोचा नहीं था, इसलिए मुझे आश्चर्य है कि अगर कोई ऐसा सुझाव दे सकता है जो सभी कंटेनर सी से मेल खाएगा। हो सकता है कि कुछ विशेषता-आश हो जो कुछ आवश्यक इटेटरेटर हो ?

बहुत धन्यवाद!

अद्यतन (और समाधान)

चैनल 9 पर इस समस्या को फिर से उठाने के बाद, मुझे स्वेन ग्रूट से एक शानदार जवाब मिला, जो कि एसएफआईएनएई प्रकार के लक्षण के साथ संयुक्त है, यह पूरी तरह से सामान्य और घोंसले फैशन में समस्या को हल करने के लिए प्रतीत होता है। Delimiters व्यक्तिगत रूप से विशिष्ट हो सकता है, std :: सेट के लिए एक उदाहरण विशेषज्ञता शामिल है, साथ ही कस्टम delimiters का उपयोग करने का एक उदाहरण भी शामिल है।

सहायक "wrap_array ()" कच्चे सी सरणी मुद्रित करने के लिए उपयोग किया जा सकता है। अद्यतन: प्रिंटिंग के लिए जोड़े और ट्यूपल उपलब्ध हैं; डिफ़ॉल्ट डिलीमीटर राउंड ब्रैकेट होते हैं।

सक्षम-अगर प्रकार की विशेषता के लिए सी ++ 0x की आवश्यकता होती है, लेकिन कुछ संशोधनों के साथ इसे C ++ 98 संस्करण बनाना संभव होना चाहिए। टुपल्स को विविध टेम्पलेट की आवश्यकता होती है, इसलिए सी ++ 0x।

मैंने स्वेन से यहां समाधान पोस्ट करने के लिए कहा है ताकि मैं इसे स्वीकार कर सकूं, लेकिन इस बीच मैं कोड को संदर्भ के लिए स्वयं पोस्ट करना चाहता हूं। ( अद्यतन: स्वेन ने अब अपना कोड नीचे पोस्ट किया है, जिसे मैंने स्वीकार्य उत्तर दिया है। मेरा स्वयं का कोड कंटेनर प्रकार के लक्षणों का उपयोग करता है, जो मेरे लिए काम करता है लेकिन यह गैर-कंटेनर कक्षाओं के साथ अप्रत्याशित व्यवहार कर सकता है जो इटरेटर प्रदान करता है।)

शीर्षलेख (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 पर एक अलग सवाल है ! अपडडेट: अब इसे लागू किया गया है, ज़ीओ के लिए धन्यवाद!
  • नामस्थान जोड़ें ताकि सहायक कक्षाएं वैश्विक नामस्थान में खून न हों। किया हुआ
  • कस्टम डेलीमीटर कक्षाओं, या शायद प्रीप्रोसेसर मैक्रोज़ बनाने में सुविधा के लिए टेम्पलेट उपनाम (या कुछ समान) जोड़ें?

हाल के अद्यतन:

  • मैंने प्रिंट फ़ंक्शन में लूप के लिए सरल के पक्ष में कस्टम आउटपुट इटरेटर को हटा दिया।
  • सभी कार्यान्वयन विवरण अब pretty_print नामस्थान में हैं। वैश्विक स्ट्रीम ऑपरेटर और pretty_print_array केवल वैश्विक नेमस्पेस में हैं।
  • नेमस्पेसिंग को फिक्स्ड करें ताकि operator<< अब std में सही हो।

टिप्पणियाँ:

  • आउटपुट इटरेटर को हटाने का मतलब है कि सुंदर प्रिंटिंग प्राप्त करने के लिए std::copy() का उपयोग करने का कोई तरीका नहीं है। यदि यह एक वांछित सुविधा है, तो मैं सुंदर इटरेटर को बहाल कर सकता हूं, लेकिन नीचे दिए गए स्वेन के कोड में कार्यान्वयन है।
  • यह ऑब्जेक्ट स्थिरांक के बजाय delimiters संकलन-समय स्थिरांक बनाने के लिए एक सचेत डिजाइन निर्णय था। इसका मतलब है कि आप रनटाइम पर गतिशील रूप से डिलीमीटर की आपूर्ति नहीं कर सकते हैं, लेकिन इसका मतलब यह भी है कि कोई अनियंत्रित ओवरहेड नहीं है। डेनिस ज़िकिफोज द्वारा ऑब्जेक्ट-आधारित डेलीमीटर कॉन्फ़िगरेशन का प्रस्ताव नीचे दिए गए स्वेन कोड पर टिप्पणी में किया गया है। अगर वांछित है, तो इसे एक वैकल्पिक सुविधा के रूप में लागू किया जा सकता है।
  • वर्तमान में यह स्पष्ट नहीं है कि नेस्टेड कंटेनर डिलीमीटर को कैसे अनुकूलित किया जाए।
  • ध्यान रखें कि इस पुस्तकालय का उद्देश्य त्वरित कंटेनर प्रिंटिंग सुविधाओं की अनुमति देना है जिसके लिए आपके हिस्से पर शून्य कोडिंग की आवश्यकता है । यह एक सर्व उद्देश्य प्रारूपण पुस्तकालय नहीं है, बल्कि कंटेनर निरीक्षण के लिए बॉयलर-प्लेट कोड लिखने की आवश्यकता को कम करने के लिए एक विकासशील टूल है।

योगदान देने वाले हर किसी के लिए धन्यवाद!

नोट: यदि आप कस्टम डिलीमीटरों को तैनात करने का एक त्वरित तरीका ढूंढ रहे हैं, तो टाइप एरर का उपयोग करने का एक तरीका यहां है। हम मानते हैं कि आपने पहले ही एक डिलीमीटर कक्षा का निर्माण किया है, 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; लिखने में सक्षम होना चाहते हैं std::cout << MyPrinter(v) << std::endl; उन कंटेनर का उपयोग कर कुछ कंटेनर v लिए। MyPrinter एक टाइप- 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); }

यह समाधान कुछ बदलावों के साथ मार्सेलो के समाधान से प्रेरित था:

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

मार्सेलो के संस्करण की तरह, यह एक is_container प्रकार विशेषता का उपयोग करता है जो समर्थित सभी कंटेनरों के लिए विशिष्ट होना चाहिए। value_type , value_type , const_iterator begin() / end() जांच करने के लिए एक विशेषता का उपयोग करना संभव हो सकता है, लेकिन मुझे यकीन नहीं है कि मैं अनुशंसा करता हूं क्योंकि यह उन मानदंडों से मेल खा सकता है जो वास्तव में कंटेनर नहीं हैं, जैसे std::basic_string । मार्सेलो के संस्करण की तरह, यह टेम्पलेट का उपयोग करता है जिसे उपयोग करने के लिए डिलीमीटर निर्दिष्ट करने के लिए विशिष्ट किया जा सकता है।

मुख्य अंतर यह है कि मैंने अपने संस्करण को pretty_ostream_iterator आस-पास बनाया है, जो std::ostream_iterator समान काम करता है लेकिन अंतिम आइटम के बाद एक डिलीमीटर मुद्रित नहीं करता है। कंटेनरों को स्वरूपित करना print_container_helper द्वारा किया जाता है, जिसका उपयोग सीधे कंटेनर को is_container विशेषता के बिना मुद्रित करने के लिए किया जा सकता है, या एक अलग डिलीमीटर प्रकार निर्दिष्ट करने के लिए किया जा सकता है।

मैंने is_container और delimiters को भी परिभाषित किया है, इसलिए यह गैर-मानक भविष्यवाणियों या आवंटकों वाले कंटेनरों के लिए और char और wchar_t दोनों के लिए काम करेगा। ऑपरेटर << फ़ंक्शन स्वयं को char और wchar_t धाराओं दोनों के साथ काम करने के लिए भी परिभाषित किया गया है।

अंत में, मैंने std::enable_if उपयोग किया है, जो सी ++ 0x के हिस्से के रूप में उपलब्ध है, और विजुअल सी ++ 2010 और जी ++ 4.3 में काम करता है (-std = c ++ 0x ध्वज की आवश्यकता है) और बाद में। इस तरह बूस्ट पर कोई निर्भरता नहीं है।


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

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

#include <boost/utility/enable_if.hpp>

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

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

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

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

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

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

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

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

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

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

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


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

The basics are here

Essentially what you do is:

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

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

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

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


The goal here is to use ADL to do customization of how we pretty print.

You pass in a formatter tag, and override 4 functions (before, after, between and descend) in the tag's namespace. This changes how the formatter prints 'adornments' when iterating over containers.

A default formatter that does {(a->b),(c->d)} for maps, (a,b,c) for tupleoids, "hello" for strings, [x,y,z] for everything else included.

It should "just work" with 3rd party iterable types (and treat them like "everything else").

If you want custom adornments for your 3rd party iterables, simply create your own tag. It will take a bit of work to handle map descent (you need to overload pretty_print_descend( your_tag to return pretty_print::decorator::map_magic_tag<your_tag> ). Maybe there is a cleaner way to do this, not sure.

A little library to detect iterability, and tuple-ness:

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

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

A library that lets us visit the contents of an iterable or tuple type object:

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

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

A pretty printing library:

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

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

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

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

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

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

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

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

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

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

टेस्ट कोड:

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

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

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

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

live example

This does use C++14 features (some _t aliases, and auto&& lambdas), but none are essential.







pretty-print