Güzel baskı C ++ STL kapları


389

Lütfen bu yayının sonundaki güncellemeleri not edin.

Güncelleme: Bu kütüphane için GitHub'da halka açık bir proje oluşturdum !


Bir kez ve herkes için tüm STL kaplarını güzel bir şekilde basmakla ilgilenen tek bir şablona sahip olmak istiyorum operator<<. Sahte kodda, ben böyle bir şey arıyorum:

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

Şimdi burada mümkün olduğunu hiç düşünmediğim kadar şablon sihir gördüm, bu yüzden herkes tüm kaplar C ile eşleşen bir şey önerebilir mi merak ediyorum. Belki bir şey gerekli yineleyici olup olmadığını anlayabilecek bir özellik-ish ?

Çok teşekkürler!


Güncelleme (ve çözüm)

Kanal 9'da bu sorunu tekrar gündeme getirdikten sonra getirdikten sonra, Sven Groot'tan, biraz SFINAE tipi özelliklerle birlikte, sorunu tamamen genel ve nesnel bir şekilde çözdüğü fantastik bir cevap aldım. Sınırlayıcılar ayrı ayrı özelleştirilebilir, std :: set için örnek bir uzmanlığın yanı sıra özel sınırlayıcıların kullanımına bir örnek eklenir.

"Wrap_array ()" yardımcısı ham C dizilerini yazdırmak için kullanılabilir. Güncelleme: Çiftler ve tuples baskı için kullanılabilir; varsayılan sınırlayıcılar yuvarlak parantezlerdir.

Enable-if türü özelliği C ++ 0x gerektirir, ancak bazı değişikliklerle bunun C ++ 98 sürümünü yapmak mümkün olmalıdır. Tuples varadik şablonları gerektirir, bu nedenle C ++ 0x.

Sven'i kabul etmek için buraya çözümü göndermesini istedim, ancak bu arada kodu referans olarak kendim göndermek istiyorum. ( Güncelleme: Sven şimdi kodunu gönderdi, bu da kabul edilen cevabı verdim. Kendi kodum benim için çalışan, ancak yineleyiciler sağlayan kapsayıcı olmayan sınıflarda beklenmedik davranışlara neden olabilecek kapsayıcı türü özellikleri kullanıyor.)

Üstbilgi (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

Kullanım örneği:

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

İyileştirmeler için diğer fikirler:

  • Çıktıyı std::tuple<...>aynı şekilde uygulayacağız std::pair<S,T>. Güncelleme: Bu artık SO hakkında ayrı bir soru ! Güncelleme: Bu, Xeo sayesinde şimdi uygulandı!
  • Yardımcı sınıfların genel ad alanına sızmaması için ad alanları ekleyin. Bitti
  • Özel sınırlayıcı sınıfları veya önişlemci makroları oluşturmayı kolaylaştırmak için şablon takma adları (veya benzer bir şey) ekleyin?

Son güncellemeler:

  • Özel çıktı yineleyiciyi yazdırma işlevinde basit bir döngü lehine kaldırdım.
  • Tüm uygulama ayrıntıları artık pretty_printad alanında. Genel pretty_print_arrayad alanında yalnızca genel akış işleçleri ve sarmalayıcı bulunur.
  • Ad alanı düzeltildi, böylece operator<<şimdi doğru bir şekilde girilecek std.

Notlar:

  • Çıktı yineleyicinin çıkarılması, std::copy()güzel baskı almak için kullanmanın bir yolu olmadığı anlamına gelir . Bu istenen bir özellik ise güzel yineleyici eski durumuna getirebilir, ancak Sven'in kodu aşağıdaki uygulamaya sahiptir.
  • Sınırlayıcıları nesne sabitlerinden ziyade derleme zamanı sabitleri yapmak bilinçli bir tasarım kararıdır. Bu, sınırlayıcıları çalışma zamanında dinamik olarak sağlayamayacağınız anlamına gelir, ancak aynı zamanda gereksiz bir ek yükün olmadığı anlamına gelir. Sven'in aşağıdaki koduna yaptığı yorumda Dennis Zickefoose tarafından nesne tabanlı bir sınırlayıcı yapılandırması önerilmiştir. İstenirse, bu alternatif bir özellik olarak uygulanabilir.
  • Şu anda iç içe geçmiş konteyner sınırlayıcılarının nasıl özelleştirileceği açık değildir.
  • Bu kütüphanenin amacının sizin için sıfır kodlama gerektiren hızlı konteyner baskı olanaklarına izin vermek olduğunu unutmayın. Çok amaçlı bir biçimlendirme kütüphanesi değil, konteynır incelemesi için kazan plakası kodu yazma ihtiyacını hafifletmek için gelişen bir araçtır.

Katkıda bulunan herkese teşekkürler!


Not: Özel sınırlayıcıları dağıtmanın hızlı bir yolunu arıyorsanız, tür silmeyi kullanmanın bir yolu aşağıdadır. Zaten bir sınırlayıcı sınıf oluşturduğunuzu varsayıyoruz MyDel, örneğin:

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

Şimdi bu sınırlayıcıları kullanarak std::cout << MyPrinter(v) << std::endl;bazı kaplar için yazabilmek istiyoruz v. MyPrinterşöyle bir tür silme sınıfı olacaktır:

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

Kodunuz çalışmaz. konteyner C gibi bir anahtar kelime yok
the_drow

31
@the_drow: OP'nin zaten bildiği anlaşılıyor. Sadece ne aradıklarını gösteriyorlar.
Marcelo Cantos

Gerçekten, sadece "ahlaki" sahte kod örneği verdim. (Ayrıca dönüş türünü de bıraktım, dikkat çekiyorum.) Emin olmak için, sınırlayıcıları değiştirilebilir hale nasıl getirebileceğimi bile bilmiyorum.
Kerrek SB

1
Başka bir alternatif, operatörleri pretty_printad alanına koymak ve kullanıcının yazdırma sırasında kullanması için bir sargı sağlamak olabilir. Kullanıcı açısından: std::cout << pretty_print(v);(muhtemelen farklı bir isimle). Ardından, operatöre sarıcı ile aynı ad alanında sağlayabilir ve daha sonra istediğiniz her şeyi güzel bir şekilde bastırabilirsiniz. Ayırıcıyı ayrıca isteğe bağlı olarak her çağrıda kullanılacak ayırıcıyı tanımlamaya izin vererek geliştirebilirsiniz (tüm uygulama için aynı seçeneği zorlayan özellikleri kullanmak yerine) \
David Rodríguez - dribeas

1
Lütfen "güncelleme" yanıtlarınızı, komik bir soru yerine gerçek cevaplara dönüştürün.
einpoklum

Yanıtlar:


82

Bu çözüm, birkaç değişiklikle Marcelo'nun çözümünden ilham aldı:

#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'nun sürümü gibi, desteklenecek tüm kaplar için uzman olması gereken bir is_container türü özellik kullanır. Bu kontrol etmek için bir özellik kullanmak mümkün olabilir value_type, const_iterator, begin()/ end()ama bu sanki bu ölçütlere uyan ama aslında kaplar olmayan şeyler eşleşebileceğini beri Eminim tavsiye ederim değilim std::basic_string. Ayrıca Marcelo'nun sürümü gibi, kullanılacak sınırlayıcıları belirtmek için özelleştirilebilen şablonlar kullanır.

En büyük fark, pretty_ostream_iteratorsürümümü benzer bir şekilde çalışan std::ostream_iteratorancak son öğeden sonra bir sınırlayıcı yazdırmayan bir etrafında oluşturmuş olmamdır. Kapların biçimlendirilmesi, print_container_helperis_container özelliği olmadan kapları doğrudan yazdırmak veya farklı bir sınırlayıcı türü belirtmek için kullanılabilen the ile yapılır .

Ayrıca standart olmayan tahminler veya ayırıcılar ile konteynerler ve hem char ve wchar_t için çalışacak şekilde is_container ve sınırlayıcılar tanımladım. İşleç << işlevinin kendisi de char ve wchar_t akışlarıyla çalışacak şekilde tanımlanır.

Son olarak, kullandım std::enable_if, hangi C ++ 0x bir parçası olarak kullanılabilir ve Visual C ++ 2010 ve g ++ 4.3 (-std = c ++ 0x bayrağı gerekir) ve daha sonra çalışır. Bu şekilde Boost'a bağımlılık olmaz.


Bunu doğru okuyorsam gibi bir çift baskı olması için <i, j>bir işlev gibi [i j]başka, sen bu tür geçmek için statik üyeleri bir avuç yepyeni türünü tanımlamak zorunda print_container_helper? Bu aşırı karmaşık görünüyor. Neden gerçek bir nesneyle, duruma göre ayarlayabileceğiniz alanlar ve farklı varsayılan değerler sağlayan uzmanlıklar ile gitmiyorsunuz?
Dennis Zickefoose

Bu şekilde görün: Kişisel olarak sevdiğiniz bir grup sınırlayıcı varsa, statik üyelerle bir kez ve herkes için birkaç sınıf oluşturabilir ve sonra bunları kullanabilirsiniz. Tabii ki kullanmanın print_container_helpersadece kadar zarif olmadığı konusunda haklısınız operator<<. Elbette kaynağı her zaman değiştirebilir veya en sevdiğiniz kapsayıcı için açık uzmanlıklar ekleyebilirsiniz, örneğin, pair<int, int>ve için pair<double, string>. Sonuçta bu, kolaylığa karşı bir tartım gücü meselesidir. İyileştirme önerileri hoş geldiniz!
Kerrek SB

... ve bunu takip etmek için, farklı veri biçimlerinde aynı veri türünün durumsal yazdırılmasına ihtiyacınız varsa , muhtemelen en azından bir küçük sargı yazmanız gerekecektir. Bu, son derece yapılandırılabilir bir biçimlendirme kütüphanesi değil, kapsayıcıları düşünmeden sihirli bir şekilde yazdırmanıza izin veren sıfır çabaya duyarlı varsayılan bir kütüphane ... (Ancak daha fazla küresel esneklik istiyorsanız , muhtemelen # makrolar ekleyebiliriz. varsayılanları manipüle etmek kolaydır.)
Kerrek SB

Asıl sorun, özel sınırlayıcılar için parametreleri kullanmak için print_container_helper'ı kolayca değiştirebilmeme rağmen, sınırlayıcılar şablonunu uzmanlaştırmaktan başka bir iç kap (veya çift) için sınırlayıcılar belirtmenin gerçekten bir yolu olmadığıdır. Bunu başarmak çok karmaşık olurdu.
Sven

Neredeyse tip silme kullanarak uygun bir özel sınırlayıcı çözüm elde etmeyi başardım. Zaten bir sınırlayıcı sınıfınız varsa MyDels, diyebilirim std::cout << CustomPrinter<MyDels>(x);. Ne edemez anda yapmak demek olduğunu std::cout << CustomDelims<"{", ":", "}">(x);sen olamaz, çünkü const char *şablon argümanları. Sınırlayıcıları derleme zamanı sabit yapma kararı, kullanım kolaylığına bazı kısıtlamalar getirir, ancak bence buna değer.
Kerrek SB

22

Bu birkaç kez düzenlendi ve RangePrinter koleksiyonunu saran ana sınıfı aramaya karar verdik

Bir kerelik << aşırı yükü yazdıktan sonra bu koleksiyonun otomatik olarak çalışması gerekir, ancak haritaların çifti yazdırması için özel bir taneye ihtiyacınız olacaktır ve sınırlayıcıyı orada özelleştirmek isteyebilirsiniz.

Ayrıca, doğrudan çıktı almak yerine öğede kullanmak için özel bir "yazdırma" işleviniz de olabilir. STL algoritmaları gibi biraz özel tahminleri geçmenizi sağlar. Harita ile bu şekilde kullanabilirsiniz, std :: pair için özel bir yazıcı ile.

"Varsayılan" yazıcınız yalnızca akışa gönderilir.

Tamam, özel bir yazıcı üzerinde çalışalım. Dış sınıfımı RangePrinter olarak değiştireceğim. Bu yüzden 2 yineleyicimiz ve bazı sınırlayıcılarımız var, ancak gerçek öğelerin nasıl yazdırılacağını özelleştirmedik.

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

Artık varsayılan olarak, anahtar ve değer türlerinin her ikisi de yazdırılabilir olduğu sürece ve başka bir öğe yazıcınıza koymadığınız zaman (başka türlerde yapabileceğiniz gibi) veya istemediğinizde haritalar için çalışacaktır. = sınırlayıcı olarak.

Bunları sonuna kadar oluşturmak için serbest fonksiyonu hareket ettiriyorum:

Bir serbest işlev (yineleyici sürümü) böyle bir şeye benzeyebilir ve hatta varsayılanlarınız olabilir:

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

Daha sonra std :: set by için kullanabilirsiniz.

 std::cout << outputFormatter( mySet );

Ayrıca, özel bir yazıcı ve iki yineleyici alan ücretsiz işlev sürümü de yazabilirsiniz. Her durumda, sizin için şablon parametrelerini çözecekler ve bunları geçici olarak geçirebileceksiniz.


Anlıyorum. Bu Marcelo Cantos'un fikrine benzer, değil mi? Bunu çalışan bir örneğe dönüştürmeye çalışacağım, teşekkürler!
Kerrek SB

Bu çözümü Marcelo'dan çok daha temiz buluyorum ve aynı esnekliği sunuyor. Ben bir çıkış açıkça bir işlev çağrısı sarmak zorunda yönü seviyorum. Gerçekten havalı olmak için, bir dizi yineleyiciyi doğrudan çıktılamak için destek ekleyebilirsiniz, böylece yapabilirim std::cout << outputFormatter(beginOfRange, endOfRange);.
Björn Pollex

1
@CashCow: Bu çözümle ilgili bir sorun var, özyinelemeli koleksiyonlarla (yani koleksiyon koleksiyonları) çalışmıyor gibi görünüyor. std::pair"iç koleksiyon" un en temel örneğidir.
Matthieu M.

Bu cevabı çok beğeniyorum, çünkü bağımlılıkları yok ve desteklediği kapları bilmesine gerek yok. std::mapKolayca idare edip edemeyeceğini ve koleksiyon koleksiyonları için işe yarayıp yaramadığını anlayabilir miyiz ? Yine de bunu bir cevap olarak kabul etmeye cazipim. Umarım Marcelo umursamaz, çözümü de işe yarar.
Kerrek SB

@Matthieu M. İç koleksiyonu nasıl bastığınıza bağlıdır. Sadece os << open << * iter << close kullanıyorsanız, bununla ilgili bir sorun yaşarsınız, ancak kullanıcılarınızın önerdiğim gibi özel bir yazıcıya geçmesine izin verirseniz, istediğiniz herhangi bir şeyi yazdırabilirsiniz.
CashCow

14

Tam bir çalışma programı olarak sunulan ve birlikte hacklediğim bir çalışma kütüphanesi:

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

Şu anda sadece vectorve ile çalışmaktadır set, ancak sadece IsContaineruzmanlık alanlarını genişleterek çoğu kapla çalışabilir . Bu kod en az olup olmadığını çok düşünmedim, ama hemen gereksiz olarak dışarı şerit olabilir bir şey düşünemiyorum.

EDIT: Sadece tekmeler için, dizileri işleyen bir sürümü dahil. Daha fazla belirsizlikten kaçınmak için karakter dizilerini dışlamak zorunda kaldım; yine de başı belaya girebilir wchar_t[].


2
@Nawaz: Dediğim gibi, bu sadece bir çözümün başlangıcı. Sen destekleyebilecek std::map<>operatörü uzmanlaşmış tarafından veya bir tanımlayarak ya operator<<için std::pair<>.
Marcelo Cantos

Ancak, Delimssınıf şablonunu kullanmak için +1 !
Nawaz

@MC: Ah güzel. Bu çok umut verici görünüyor! (Bu arada, "std :: ostream &" dönüş türüne ihtiyacınız var, bunu ilk başta unutmuştum.)
Kerrek SB

Hmm, bunu bir std :: vector <int> ve std :: set <std :: string> üzerinde denediğimde "belirsiz aşırı yük"
alıyorum

Evet, şu anda operator<<şablonun hemen hemen her şeyle eşleşmesinden kaynaklanan belirsizlikleri nasıl önleyeceğimizi anlıyorum .
Marcelo Cantos

10

{Fmt} kitaplığını kullanarak kapları, aralıkları ve tuples'ları biçimlendirebilirsiniz . Örneğin:

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

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

baskılar

{1, 2, 3}

için stdout.

Feragatname : {fmt} yazarıyım.


8

Kod şimdi birkaç kez kullanışlı olduğunu kanıtladı ve kullanımı oldukça düşük olduğundan özelleştirme almak için masraf hissediyorum. Bu nedenle, MIT lisansı altında yayınlamaya ve üstbilginin ve küçük bir örnek dosyanın indirilebileceği bir GitHub deposu sağlamaya karar verdim .

http://djmuw.github.io/prettycc

0. Önsöz ve ifadeler

Bu cevap açısından bir 'dekorasyon' bir önek dizesi, sınırlayıcı dizesi ve bir postfix dizesi dizisidir. Ön ek dizesinin bir akışa önce bir akışa ve postfix dizesinin bir kabın değerlerinden sonra yerleştirildiği yer (bkz. 2. Hedef kapsayıcılar). Sınırlayıcı dize, ilgili kabın değerleri arasına eklenir.

Not: Aslında, bu yanıt soruyu% 100'e yöneltmez, çünkü dekorasyon kesinlikle sabit bir zaman sabiti derlenmez, çünkü mevcut akışa özel bir dekorasyonun uygulanıp uygulanmadığını kontrol etmek için çalışma zamanı kontrolleri gereklidir. Yine de, bazı iyi özelliklere sahip olduğunu düşünüyorum.

Not2: Henüz iyi test edilmediğinden küçük hatalar olabilir.

1. Genel fikir / kullanım

Kullanım için sıfır ek kod gerekli

Bu kadar kolay tutulmalıdır

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

Kolay özelleştirme ...

... belirli bir akış nesnesiyle ilgili olarak

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

veya tüm akışlarla ilgili olarak:

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

Kaba açıklama

  • Kod, her tür için varsayılan bir dekorasyon sağlayan bir sınıf şablonu içerir
  • (a) belirli tip (ler) için varsayılan dekorasyonu değiştirmek üzere uzmanlaşabilir ve
  • belirli bir akışta belirli bir türü özel olarak süsleyen bir nesneye bir işaretçi kaydetmek için / ios_basekullanarak sağlanan özel depolamayı kullanma .xallocpwordpretty::decor

pretty::decor<T>Bu akış için hiçbir nesne açıkça ayarlanmamışsa pretty::defaulted<T, charT, chartraitT>::decoration(), verilen tür için varsayılan dekorasyonu elde etmek üzere çağrılır. Sınıf pretty::defaulted, varsayılan dekorasyonları özelleştirmek için uzmanlaşacaktır.

2. Nesneleri / kapsayıcıları hedefleyin

Bu kodun 'güzel dekorasyonu'obj için hedef nesneler ,

  • aşırı yükler std::beginvestd::end tanımlanmış (C Stili dizileri içerir),
  • sahip olan begin(obj)ve end(obj)ADL ile kullanılabilir,
  • türü std::tuple
  • veya tür std::pair.

Kod, aralık özelliklerine ( begin/ end) sahip sınıfların tanımlanması için bir özellik içerir . ( begin(obj) == end(obj)Geçerli bir ifade olup olmadığı kontrol edilmez .)

Kod, operator<<yalnızca daha özel bir sürümüne sahip olmayan sınıflar için geçerli olan genel ad alanında s sağlar operator<<. Bu nedenle, örneğin std::string, geçerli bir begin/ endçift olmasına rağmen bu koddaki operatör kullanılarak yazdırılmaz .

3. Kullanım ve özelleştirme

Süslemeler her tür (farklı tuples hariç ) ve akış (akış tipi değil!) İçin ayrı ayrı uygulanabilir . (Yanistd::vector<int> farklı akış nesneleri için farklı süslemeler olabilir.)

A) Varsayılan dekorasyon

Varsayılan önek, ""varsayılan postfix gibi (hiçbir şey), varsayılan sınırlayıcı ", "(virgül + boşluk) şeklindedir.

B) pretty::defaultedSınıf şablonunu uzmanlaştırarak tipin özelleştirilmiş varsayılan dekorasyonu

struct defaultedStatik üye işlevi vardır decoration()dönen decorverilen türü için varsayılan değerler içerir nesne.

Dizi kullanma örneği:

Varsayılan dizi yazdırmayı özelleştirin:

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

Bir diziyi yazdırın:

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)

Kullanılması PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)için makro charakışları

Makro,

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

yukarıdaki kısmi uzmanlığın yeniden yazılmasını sağlamak

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

veya aşağıdakiler gibi tam bir uzmanlık eklemek:

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

İçin bir başka makro wchar_takışları dahildir: PRETTY_DEFAULT_WDECORATION.

C) Akarsulara dekorasyon uygulayın

İşlev pretty::decoration, belirli bir akışa bir dekorasyon uygulamak için kullanılır. Aşırı yükler var - sınırlayıcı olan bir dize argümanı (varsayılan sınıftan önek ve postfix'i benimseme) - veya tüm dekorasyonu birleştiren üç dize argümanı

Verilen tip ve akış için komple dekorasyon

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

Verilen akış için sınırlayıcının özelleştirilmesi

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. Özel kullanım std::tuple

Bu kod, olası her demet tipi için bir uzmanlaşmaya izin vermek yerine, her std::tuple<void*>türlü std::tuple<...>s için mevcut olan tüm dekorasyonları uygular .

5. akıştan özel dekorasyon Kaldır

Belirli bir tür için varsayılan dekorasyona geri dönmek için pretty::clearakıştaki işlev şablonunu kullanın s.

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

5. Diğer örnekler

Yeni satır sınırlayıcıyla "matris benzeri" yazdırma

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;

Baskılar

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

Üzerinde görmek ideone / KKUebZ

6. Kod

#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

Buraya başka bir cevap ekleyeceğim, çünkü öncekime farklı bir yaklaşım getirdim ve bu da yerel ayarları kullanmak.

Temel bilgiler burada

Temelde yaptığınız şey:

  1. Türetilmiş bir sınıf oluşturun std::locale::facet . Hafif dezavantajı, kimliğini tutmak için bir yerde bir derleme birimine ihtiyacınız olacaktır. Buna MyPrettyVectorPrinter diyelim. Muhtemelen daha iyi bir isim verirsiniz ve ayrıca eş ve harita için bir isim yaratırsınız.
  2. Akış işlevinizde, std::has_facet< MyPrettyVectorPrinter >
  3. Bu doğruysa, std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. Faset nesneleriniz, sınırlayıcılar için değerlere sahip olacak ve bunları okuyabilirsiniz. Özellik bulunmazsa, yazdırma işleviniz ( operator<<) varsayılan işlevler sağlar. Aynı şeyi bir vektör okumak için de yapabileceğinizi unutmayın.

Yine de özel bir geçersiz kılmayı kullanabiliyorken varsayılan bir baskı kullanabileceğiniz için bu yöntemi seviyorum.

Dezavantajları, birden fazla projede kullanılıyorsa fasetiniz için bir kütüphaneye ihtiyaç duyuyor (bu yüzden sadece üstbilgiler olamaz) ve ayrıca yeni bir yerel nesne oluşturma masrafına dikkat etmeniz gerekiyor.

Bunu, diğer yaklaşımımı değiştirmek yerine yeni bir çözüm olarak yazdım çünkü her iki yaklaşımın da doğru olabileceğine ve seçiminizi yaptığınıza inanıyorum.


Bunu düzleştireyim: Bu yaklaşımla, kullanmak istediğim her kap tipini aktif olarak beyaz listeye eklemem gerekir mi?
Kerrek SB

Gerçekten bir kendi std olanlar dışında std uzatmak gerekir, ancak her kapsayıcı türü (vektör, harita, liste, deque) artı yazdırmak istediğiniz çifti için aşırı bir operatör << yazın. Tabii ki bazıları bir faseti paylaşabilir (örneğin, liste, vektör ve deque de aynı şekilde yazdırmak isteyebilirsiniz). "Varsayılan" bir yazdırma yöntemi sağlarsınız, ancak kullanıcıların yazdırmadan önce bir model, yerel ayar ve imbue oluşturmasına izin verirsiniz. Biraz artırmak gibi onların date_time yazdırır. Ayrıca, varsayılan olarak bu şekilde yazdırmak için yönlerini global yerel ayarlara yükleyebilir.
CashCow

2

Buradaki amaç, güzel baskı yöntemimizi özelleştirmek için ADL'yi kullanmaktır.

Bir biçimlendirici etiketi geçirirsiniz ve etiketin ad alanında 4 işlevi (önce, sonra, arasında ve inişi) geçersiz kılarsınız. Bu, biçimlendiricinin kaplar üzerinde yineleme yaparken 'süslemeleri' yazdırma biçimini değiştirir.

{(a->b),(c->d)}Haritalar, (a,b,c)tupleoidler, "hello"dize [x,y,z]ve diğer her şey için varsayılan bir biçimlendirici .

3. parti yinelenebilir türleri ile "sadece çalışmak" (ve "her şey" gibi davranın).

3. taraf yinelemeleriniz için özel süslemeler istiyorsanız, kendi etiketinizi oluşturmanız yeterlidir. Harita inişini işlemek biraz zaman alacaktır ( pretty_print_descend( your_taggeri dönmek için aşırı yüklemeniz gerekir pretty_print::decorator::map_magic_tag<your_tag>). Belki bunu yapmanın daha temiz bir yolu var, emin değilim.

Yinelenebilirliği ve özetliği algılayan küçük bir kütüphane:

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

Yinelenebilir veya grup tipi bir nesnenin içeriğini ziyaret etmemizi sağlayan bir kütüphane:

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

Güzel bir baskı kütüphanesi:

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

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

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

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

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

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

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

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

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

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

Test kodu:

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

canlı örnek

Bu, C ++ 14 özelliklerini (bazı _ttakma adlar ve auto&&lambdalar) kullanır, ancak hiçbiri zorunlu değildir.


@KerrekSB çalışma sürümü, bazı değişiklikler ile. Kod toplu genel "ziyaret tuples / Iterables" ve fantezi biçimlendirme (dahil olduğu ->içinde pairbir s mapbu noktada s). Güzel baskı kütüphanesinin çekirdeği güzel ve küçük, güzel. Kolayca genişletilebilir hale getirmeye çalıştım, başarılı olup olmadığımdan emin değilim.
Yakk - Adam Nevraumont

1

Benim çözümüm scc paketinin bir parçası olan simple.h . Tüm standart konteynerler, haritalar, kümeler, c-dizileri yazdırılabilir.


İlginç. Kaplar için şablon şablonu yaklaşımını seviyorum, ancak standart olmayan tahminlere veya ayırıcılara sahip özel kaplar ve STL kapları için çalışıyor mu? (Ben varadik şablonları kullanarak C ++ 0x bir bimap uygulamak için benzer bir şey yaptım .) Ayrıca, yineleyiciler yazdırma yordamları için genel olarak kullanmak gibi görünmüyor; neden bir sayacın açık kullanımı i?
Kerrek SB

Standart dışı tahminlere sahip konteyner nedir? İmza ile eşleşen özel kap yazdırılacaktır. Standart olmayan ayırıcılar şu anda desteklenmemektedir, ancak düzeltilmesi kolaydır. Şimdilik buna ihtiyacım yok.
Leonid Volnitsky

Yineleyiciler yerine dizin kullanmak için iyi bir neden yoktur. Tarihsel nedenler. Zamanım olduğunda bunu düzeltirim.
Leonid Volnitsky

"Standart dışı tahminlere sahip konteyner" std::setile, özel bir karşılaştırıcıya sahip a veya özel bir eşitliğe sahip unordered_map gibi bir şey demek istiyorum . Bu yapıları desteklemek çok önemli olurdu.
Kerrek SB

1

İlk BoostCon'dan (şimdi CppCon olarak adlandırılır) birinden çıkarken, ben ve iki kişi daha bunu yapmak için bir kütüphane üzerinde çalıştık. Ana yapışma noktasının ad alanı std'sini genişletmesi gerekiyordu. Bu bir destek kütüphanesi için hareketsiz olduğu ortaya çıktı.

Ne yazık ki kodun bağlantıları artık çalışmıyor, ancak tartışmalarda bazı ilginç tidbitler bulabilirsiniz (en azından neyi adlandıracağından bahsetmeyenler!)

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


0

İşte 2016 yılında yapılan uygulama sürümüm

Her şey tek bir başlıkta, bu yüzden kullanımı kolaydır 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;
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.