wake-up-neo.com

Pretty-Print-C ++ - STL-Container

Bitte beachten Sie die Updates am Ende dieses Beitrags.

Update: Ich habe ein öffentliches Projekt auf GitHub für diese Bibliothek erstellt!


Ich hätte gerne eine einzige Vorlage, die ein für alle Mal dafür sorgt, dass alle STL-Container mit operator<< Hübsch gedruckt werden. Im Pseudo-Code suche ich so etwas:

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

Jetzt habe ich hier auf SO) eine Menge Template-Magie gesehen, die ich nie für möglich gehalten hätte. Deshalb frage ich mich, ob jemand etwas vorschlagen kann, das zu allen Containern C passt kann herausfinden, ob etwas den notwendigen Iterator hat?

Danke vielmals!


Update (und Lösung)

Nachdem ich dieses Problem auf Channel 9 erneut angesprochen habe, habe ich eine fantastische Antwort von Sven Groot erhalten, die zusammen mit ein wenig SFINAE-Typ-Charakteristik das Problem auf eine völlig allgemeine und schachtelbare Weise zu lösen scheint. Die Begrenzer können individuell spezialisiert sein, eine Beispielspezialisierung für std :: set ist enthalten sowie ein Beispiel für die Verwendung benutzerdefinierter Begrenzer.

Der Helfer "wrap_array ()" kann verwendet werden, um rohe C-Arrays zu drucken. Update: Paare und Tupel stehen zum Drucken zur Verfügung; Standardtrennzeichen sind runde Klammern.

Das Merkmal enable-if type erfordert C++ 0x, aber mit einigen Änderungen sollte es möglich sein, eine C++ 98-Version davon zu erstellen. Tupel benötigen unterschiedliche Templates, daher C++ 0x.

Ich habe Sven gebeten, die Lösung hier zu posten, damit ich sie akzeptieren kann, aber in der Zwischenzeit möchte ich den Code selbst als Referenz posten. ( Update: Sven hat jetzt unten seinen Code gepostet, den ich als Antwort angenommen habe. In meinem eigenen Code werden Container-Typ-Merkmale verwendet, die für mich funktionieren, bei Nicht-Container-Klassen, die Iteratoren bereitstellen, jedoch zu unerwartetem Verhalten führen können.)

Header (prettyprint.h):

#ifndef H_PRETTY_PRINT
#define H_PRETTY_PRINT


#include <type_traits>
#include <iostream>
#include <utility>
#include <Tuple>


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

namespace pretty_print
{

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

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


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

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


    // Holds the delimiter values for a specific character type

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


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

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


    // Default delimiters

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


    // Delimiters for set

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


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

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


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

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

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

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

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

                stream << *it;
            }

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

    private:
        const T & _container;
    };


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

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

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

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

    private:
        const T & t;
    };

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

} // namespace pretty_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

Anwendungsbeispiel:

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

Weitere Verbesserungsvorschläge:

  • Implementieren Sie die Ausgabe für std::Tuple<...> Auf dieselbe Weise wie für std::pair<S,T>.  Update: Dies ist jetzt ein separate Frage zu SO ! Update: Dank Xeo!
  • Fügen Sie Namespaces hinzu, damit die Hilfsklassen nicht in den globalen Namespace übergehen.  Fertig
  • Fügen Sie Vorlagen-Aliase (oder ähnliches) hinzu, um benutzerdefinierte Begrenzerklassen oder Präprozessor-Makros zu erstellen?

Letzte Updates:

  • Ich habe den benutzerdefinierten Ausgabe-Iterator zugunsten einer einfachen for-Schleife in der Druckfunktion entfernt.
  • Alle Implementierungsdetails befinden sich jetzt im Namespace pretty_print. Nur die globalen Stream-Operatoren und der Wrapper pretty_print_array Befinden sich im globalen Namespace.
  • Der Namensraum wurde korrigiert, so dass operator<< Jetzt korrekt in std ist.

Hinweise:

  • Das Entfernen des Ausgabe-Iterators bedeutet, dass es keine Möglichkeit gibt, std::copy() zu verwenden, um einen hübschen Ausdruck zu erhalten. Ich könnte den hübschen Iterator wieder aktivieren, wenn dies eine gewünschte Funktion ist, aber der folgende Code von Sven hat die Implementierung.
  • Es war eine bewusste Entwurfsentscheidung, die Begrenzer nicht zu Objektkonstanten, sondern zu Konstanten für die Kompilierungszeit zu machen. Das bedeutet, dass Sie Begrenzer nicht zur Laufzeit dynamisch bereitstellen können, sondern dass kein unnötiger Overhead entsteht. Eine objektbasierte Begrenzerkonfiguration wurde von Dennis Zickefoose in einem Kommentar zu Svens Code unten vorgeschlagen. Falls gewünscht, könnte dies als alternatives Merkmal implementiert werden.
  • Derzeit ist nicht klar, wie verschachtelte Container-Begrenzer angepasst werden sollen.
  • Denken Sie daran, dass der Zweck dieser Bibliothek darin besteht, schnell Behälterdruckmöglichkeiten zu ermöglichen, die von Ihnen eine Nullcodierung erfordern. Es handelt sich nicht um eine universelle Formatierungsbibliothek, sondern um ein Entwicklungswerkzeug, mit dem die Notwendigkeit, Kesselplattencode für die Containerinspektion zu schreiben, verringert wird.

Vielen Dank an alle, die dazu beigetragen haben !


Hinweis: Wenn Sie nach einer schnellen Möglichkeit suchen, benutzerdefinierte Trennzeichen zu implementieren, haben Sie hier eine Möglichkeit, die Typlöschung zu verwenden. Wir gehen davon aus, dass Sie bereits eine Trennzeichenklasse erstellt haben, sagen Sie MyDel, wie folgt:

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

Jetzt wollen wir in der Lage sein, std::cout << MyPrinter(v) << std::endl; für einen Container v mit diesen Begrenzern zu schreiben. MyPrinter wird eine Klasse zum Löschen von Zeichen sein, wie folgt:

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); }
370
Kerrek SB

Diese Lösung wurde von Marcelos Lösung inspiriert, mit ein paar Änderungen:

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

Wie in Marcelos Version wird ein Merkmal vom Typ is_container verwendet, das auf alle Container spezialisiert sein muss, die unterstützt werden sollen. Es ist möglich, ein Merkmal zu verwenden, um nach value_type, const_iterator, begin()/end() zu suchen, aber ich bin mir nicht sicher, ob ich das tun würde empfehlen, da es möglicherweise mit Dingen übereinstimmt, die diesen Kriterien entsprechen, aber keine Container sind, wie std::basic_string. Ebenso wie in Marcelos Version werden Vorlagen verwendet, mit denen die zu verwendenden Trennzeichen spezifiziert werden können.

Der Hauptunterschied besteht darin, dass ich meine Version um einen pretty_ostream_iterator Erstellt habe, der ähnlich wie der std::ostream_iterator Funktioniert, aber nach dem letzten Element kein Trennzeichen ausgibt. Die Formatierung der Container erfolgt mit print_container_helper, Das direkt zum Drucken von Containern ohne das Merkmal is_container oder zur Angabe eines anderen Begrenzertyps verwendet werden kann.

Ich habe auch is_container und Delimiter definiert, damit es für Container mit nicht standardmäßigen Prädikaten oder Allokatoren sowie für char und wchar_t funktioniert. Die Operator << -Funktion selbst ist auch so definiert, dass sie sowohl mit char- als auch mit wchar_t-Streams funktioniert.

Schließlich habe ich std::enable_if Verwendet, das als Teil von C++ 0x verfügbar ist und in Visual C++ 2010 und g ++ 4.3 (benötigt das Flag -std = c ++ 0x) und höher funktioniert. Auf diese Weise besteht keine Abhängigkeit von Boost.

81
Sven

Dies wurde einige Male bearbeitet, und wir haben beschlossen, die Hauptklasse aufzurufen, die eine RangePrinter-Auflistung umschließt

Dies sollte bei jeder Sammlung automatisch funktionieren, sobald Sie den einmaligen Operator << overload geschrieben haben, mit der Ausnahme, dass Sie einen speziellen Operator für Karten benötigen, um das Paar zu drucken, und möglicherweise den Begrenzer dort anpassen möchten.

Sie können auch eine spezielle "Druck" -Funktion für das Element verwenden, anstatt es direkt auszugeben. Ähnlich wie bei STL-Algorithmen können Sie benutzerdefinierte Prädikate übergeben. Mit map würde man das so machen, mit einem benutzerdefinierten Drucker für das std :: pair.

Ihr "Standard" -Drucker würde es nur in den Stream ausgeben.

Ok, lassen Sie uns an einem benutzerdefinierten Drucker arbeiten. Ich werde meine äußere Klasse in RangePrinter ändern. Wir haben also 2 Iteratoren und einige Begrenzer, aber nicht angepasst, wie die tatsächlichen Elemente gedruckt werden sollen.

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

Jetzt funktioniert es standardmäßig für Karten, solange der Schlüssel- und der Wertetyp beide druckbar sind. Sie können einen eigenen Drucker für spezielle Objekte einrichten, wenn dies nicht der Fall ist (wie bei jedem anderen Typ) oder wenn Sie dies nicht möchten = als Begrenzer.

Ich verschiebe die freie Funktion, um diese jetzt zu erstellen:

Eine freie Funktion (Iterator-Version) würde ungefähr so ​​aussehen und Sie könnten sogar Standardeinstellungen haben:

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

Sie könnten es dann für std :: set von verwenden

 std::cout << outputFormatter( mySet );

Sie können auch eine Version mit freien Funktionen schreiben, die einen benutzerdefinierten Drucker und zwei Iteratoren umfasst. In jedem Fall lösen sie die Vorlagenparameter für Sie auf und Sie können sie als temporäre Daten weitergeben.

22
CashCow

Hier ist eine Arbeitsbibliothek, die als vollständiges Arbeitsprogramm präsentiert wird und die ich gerade zusammen gehackt habe:

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

Es funktioniert derzeit nur mit vector und set, kann jedoch für die meisten Container verwendet werden, indem die Spezialisierungen für IsContainer erweitert werden. Ich habe nicht viel darüber nachgedacht, ob dieser Code minimal ist, aber ich kann mir nichts vorstellen, was ich als redundant herausnehmen könnte.

EDIT: Nur für Kicks habe ich eine Version hinzugefügt, die Arrays handhabt. Ich musste Zeichen-Arrays ausschließen, um weitere Unklarheiten zu vermeiden. es könnte immer noch Ärger mit wchar_t[].

15
Marcelo Cantos

Der Code hat sich jetzt schon mehrmals als nützlich erwiesen, und ich habe das Gefühl, dass die Kosten für die Anpassung gering sind, da die Verwendung recht gering ist. Daher habe ich beschlossen, es unter [~ # ~] mit [~ # ~] Lizenz zu veröffentlichen und ein GitHub-Repository bereitzustellen, in dem der Header und ein kleines Beispiel enthalten sind Datei kann heruntergeladen werden.

http://djmuw.github.io/prettycc

0. Vorwort und Formulierung

Eine 'Dekoration' im Sinne dieser Antwort ist eine Reihe von Präfix-Zeichenfolgen, Trennzeichenfolgen und Postfix-Zeichenfolgen. Wobei die Präfixzeichenfolge vor und die Postfixzeichenfolge nach den Werten eines Containers in einen Stream eingefügt wird (siehe 2. Zielcontainer). Die Trennzeichenkette wird zwischen die Werte des jeweiligen Containers eingefügt.

Hinweis: In dieser Antwort wird die Frage nicht zu 100% beantwortet, da die Dekoration nicht streng kompiliert ist, da Laufzeitprüfungen erforderlich sind, um zu überprüfen, ob eine benutzerdefinierte Dekoration auf den aktuellen Stream angewendet wurde. = Trotzdem denke ich, dass es ein paar anständige Features hat.

Anmerkung2: Kann kleinere Fehler enthalten, da es noch nicht gut getestet wurde.

1. Allgemeine Idee/Verwendung

Für die Verwendung ist kein zusätzlicher Code erforderlich

Es ist so einfach wie möglich zu halten

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

Einfache Anpassung ...

... in Bezug auf ein bestimmtes Stream-Objekt

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

oder in Bezug auf alle Streams:

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

Grobe Beschreibung

  • Der Code enthält eine Klassenvorlage, die eine Standarddekoration für jeden Typ bereitstellt
  • die spezialisiert werden kann, um die Standarddekoration für (einen) bestimmten Typ (en) zu ändern, und es ist
  • verwenden des von ios_base bereitgestellten privaten Speichers mit xalloc/pword, um einen Zeiger auf ein pretty::decor - Objekt zu speichern, das einen bestimmten Typ in einem bestimmten Stream speziell dekoriert .

Wenn für diesen Stream kein pretty::decor<T> - Objekt explizit eingerichtet wurde, wird pretty::defaulted<T, charT, chartraitT>::decoration() aufgerufen, um die Standarddekoration für den angegebenen Typ zu erhalten. Die Klasse pretty::defaulted Soll darauf spezialisiert sein, Standarddekorationen anzupassen.

2. Zielobjekte/Container

Zielobjekte obj für die 'hübsche Dekoration' dieses Codes sind Objekte mit beiden

  • überladungen std::begin und std::end definiert (einschließlich C-Style-Arrays),
  • begin(obj) und end(obj) über ADL verfügbar haben,
  • sind vom Typ std::Tuple
  • oder vom Typ std::pair.

Der Code enthält ein Merkmal zur Identifizierung von Klassen mit Bereichsmerkmalen (begin/end). (Es ist keine Überprüfung enthalten, ob begin(obj) == end(obj) ein gültiger Ausdruck ist.)

Der Code enthält operator<< Im globalen Namespace, die nur für Klassen gelten, für die keine speziellere Version von operator<< Verfügbar ist. Daher wird beispielsweise std::string Nicht mit dem Operator in diesem Code gedruckt, obwohl ein gültiges begin/end-Paar vorhanden ist.

3. Nutzung und Anpassung

Dekorationen können für jeden Typ (außer für unterschiedliche Tuples) und Stream (nicht Stream-Typ!) Separat festgelegt werden. (Das heißt, ein std::vector<int> Kann unterschiedliche Dekorationen für unterschiedliche Stream-Objekte haben.)

A) Standarddekoration

Das Standardpräfix ist "" (Nichts), ebenso wie das Standardpostfix, während das Standardtrennzeichen ", " (Komma + Leerzeichen) ist.

B) Angepasste Standarddekoration eines Typs durch Spezialisierung der Klassenvorlage pretty::defaulted

Das struct defaulted Hat eine statische Elementfunktion decoration(), die ein decor -Objekt zurückgibt, das die Standardwerte für den angegebenen Typ enthält.

Beispiel mit einem Array:

Anpassen des Standard-Array-Drucks:

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

Arry-Array drucken:

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)

Verwenden des Makros PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) für Streams char

Das Makro erweitert sich zu

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

ermöglichen des Umschreibens der obigen Teilspezialisierung

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

oder Einfügen einer vollständigen Spezialisierung wie

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

Ein weiteres Makro für wchar_t - Streams ist enthalten: PRETTY_DEFAULT_WDECORATION.

C) Bäche dekorieren

Die Funktion pretty::decoration Wird verwendet, um einem bestimmten Stream eine Dekoration aufzuerlegen. Es gibt Überladungen, bei denen entweder ein String-Argument als Trennzeichen verwendet wird (wobei Präfix und Postfix aus der voreingestellten Klasse übernommen werden) oder drei String-Argumente, die die gesamte Dekoration zusammensetzen

Komplette Dekoration für gegebene Art und Strom

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

Anpassung des Trennzeichens für den angegebenen Stream

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. Spezielle Behandlung von std::Tuple

Anstatt eine Spezialisierung für jeden möglichen Tupeltyp zuzulassen, werden mit diesem Code alle für std::Tuple<void*> Verfügbaren Verzierungen auf alle Arten von std::Tuple<...> Angewendet.

5. Entfernen Sie die benutzerdefinierte Dekoration aus dem Stream

Um zur Standarddekoration für einen bestimmten Typ zurückzukehren, verwenden Sie die Funktionsvorlage pretty::clear Im Stream s.

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

5. Weitere Beispiele

Drucken "matrixartig" mit Zeilenumbruch

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;

Druckt

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

Sehen Sie es auf ideone/KKUebZ

6. Code

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

Sie können Container sowie Bereiche und Tupel mit der {fmt} -Bibliothek formatieren. Beispielsweise:

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

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

druckt

{1, 2, 3}

zu stdout.

Haftungsausschluss : Ich bin der Autor von {fmt}.

4
vitaut

Ich werde hier eine weitere Antwort hinzufügen, da ich eine andere Herangehensweise als meine vorherige entwickelt habe, nämlich die Verwendung von Gebietsschemafacetten.

Die Grundlagen sind hier

Was Sie tun, ist im Wesentlichen:

  1. Erstellen Sie eine Klasse, die von std::locale::facet Abgeleitet ist. Der kleine Nachteil ist, dass Sie eine Kompilierungseinheit benötigen, um ihre ID zu speichern. Nennen wir es MyPrettyVectorPrinter. Sie würden ihm wahrscheinlich einen besseren Namen geben und auch einen für Paar und Karte erstellen.
  2. In Ihrer Stream-Funktion überprüfen Sie std::has_facet< MyPrettyVectorPrinter >
  3. Wenn das true zurückgibt, extrahiere es mit std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. Ihre Facettenobjekte haben Werte für die Begrenzer, und Sie können sie lesen. Wenn die Facette nicht gefunden wird, bietet Ihre Druckfunktion (operator<<) Standardfunktionen. Beachten Sie, dass Sie zum Lesen eines Vektors dasselbe tun können.

Diese Methode gefällt mir, weil Sie einen Standarddruck verwenden können, während Sie eine benutzerdefinierte Überschreibung verwenden können.

Die Nachteile erfordern eine Bibliothek für Ihre Facette, wenn sie in mehreren Projekten verwendet wird (es kann also nicht nur um Kopfzeilen gehen), und auch die Tatsache, dass Sie sich vor den Kosten für die Erstellung eines neuen Gebietsschemaobjekts hüten müssen.

Ich habe dies als neue Lösung geschrieben, anstatt meine andere zu modifizieren, da ich glaube, dass beide Ansätze korrekt sein können und Sie selbst entscheiden können.

4
CashCow

Das Ziel hier ist es, ADL zu verwenden, um die Art und Weise anzupassen, wie wir hübsch drucken.

Sie übergeben ein Formatierungs-Tag und überschreiben 4 Funktionen (vor, nach, zwischen und absteigend) im Namespace des Tags. Dadurch wird geändert, wie der Formatierer beim Durchlaufen von Containern "Verzierungen" druckt.

Ein Standardformatierer, der {(a->b),(c->d)} für Maps ausführt, (a,b,c) Für Tupleoids, "hello" Für Strings, [x,y,z] Für alles andere.

Es sollte "nur" mit iterablen Typen von Drittanbietern funktionieren (und sie wie "alles andere" behandeln).

Wenn Sie individuelle Verzierungen für Ihre Iterables von Drittanbietern wünschen, erstellen Sie einfach Ihr eigenes Tag. Es wird ein wenig Arbeit erfordern, um den Kartenabstieg zu bewältigen (Sie müssen pretty_print_descend( your_tag Überladen, um pretty_print::decorator::map_magic_tag<your_tag> Zurückzugeben). Vielleicht gibt es einen saubereren Weg, nicht sicher.

Eine kleine Bibliothek zum Erkennen von Iterabilität und Tupel-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>{}> {};

Eine Bibliothek, mit der wir den Inhalt eines Objekts vom Typ iterable oder Tuple anzeigen können:

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

Eine hübsche Druckbibliothek:

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

Testcode:

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-Beispiel

Dabei werden C++ 14-Funktionen verwendet (einige Aliase _t Und auto&& Lambdas), aber keine sind unbedingt erforderlich.

Meine Lösung ist simple.h , was Teil des scc Pakets ist. Alle Standardcontainer, Karten, Sets, C-Arrays sind druckbar.

1

Als ich aus einer der ersten BoostCon (jetzt CppCon) herauskam, arbeiteten ich und zwei andere an einer Bibliothek, um genau dies zu tun. Der wichtigste Knackpunkt war die Erweiterung des Namespace std. Es stellte sich heraus, dass eine Boost-Bibliothek nicht in Frage kam.

Leider funktionieren die Links zum Code nicht mehr, aber in den Diskussionen finden Sie möglicherweise einige interessante Details (zumindest diejenigen, die nicht darüber sprechen, wie sie genannt werden sollen!)

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

1
Jeffrey Faust