Контейнеры Pretty-print C ++ STL

389

Пожалуйста, обратите внимание на обновления в конце этого поста.

Обновление: я создал публичный проект на GitHub для этой библиотеки!


Я хотел бы иметь один шаблон, который раз и навсегда позаботится о красивой печати через все контейнеры STL operator<<. В псевдокоде я ищу что-то вроде этого:

template<container C, class T, String delim = ", ", String open = "[", String close = "]">
std::ostream & operator<<(std::ostream & o, const C<T> & x)
{
    o << open;
    // for (typename C::const_iterator i = x.begin(); i != x.end(); i++) /* Old-school */
    for (auto i = x.begin(); i != x.end(); i++)
    {
        if (i != x.begin()) o << delim;
        o << *i;
    }
    o << close;
    return o;
}

Теперь я видел много магии шаблонов здесь на SO, что я никогда не думал, что это возможно, поэтому мне интересно, если кто-нибудь может предложить что-то, что будет соответствовать всем контейнерам C. Может быть, что-то trait-ish, который может выяснить, если у чего-то есть необходимый итератор ?

Большое спасибо!


Обновление (и решение)

После того , как я снова поднял эту проблему на 9-м канале , я получил фантастический ответ от Свена Гроота, который в сочетании с небольшим количеством признаков типа SFINAE, кажется, решает проблему полностью общим и нестабильным образом. Разделители могут быть индивидуально специализированными, приведена примерная специализация для std :: set, а также пример использования пользовательских разделителей.

Помощник "wrap_array ()" может быть использован для печати необработанных массивов Си. Обновление: Пары и кортежи доступны для печати; разделителями по умолчанию являются круглые скобки.

Черта типа enable-if требует C ++ 0x, но с некоторыми изменениями должна быть возможность сделать версию C ++ 98 этого. Кортежи требуют шаблонов с переменным числом, следовательно, C ++ 0x.

Я попросил Свена опубликовать здесь решение, чтобы я мог принять его, но пока я хотел бы опубликовать код самостоятельно для справки. ( Обновление: Свен теперь разместил свой код ниже, на который я сделал принятый ответ. Мой собственный код использует черты контейнерного типа, которые работают для меня, но могут вызывать неожиданное поведение с неконтейнерными классами, которые предоставляют итераторы.)

Заголовок (prettyprint.h):

#ifndef H_PRETTY_PRINT
#define H_PRETTY_PRINT


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


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

namespace pretty_print
{

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

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


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

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


    // Holds the delimiter values for a specific character type

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


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

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


    // Default delimiters

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


    // Delimiters for set

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


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

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


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

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

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

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

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

                stream << *it;
            }

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

    private:
        const T & _container;
    };


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

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

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

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

    private:
        const T & t;
    };

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

} // namespace pretty_print


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


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

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


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

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

    // Prints a container to the stream using default delimiters

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

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

        stream << value.first;

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

        stream << value.second;

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

        return stream;
    }
} // namespace std

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

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

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

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

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

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

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


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

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

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

        return stream;
    }
} // namespace std


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

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

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

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

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


#endif

Пример использования:

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

#include "prettyprint.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

Дальнейшие идеи по улучшению:

  • Реализуйте вывод для std::tuple<...>таким же образом, как у нас std::pair<S,T>. Обновление: теперь это отдельный вопрос о SO ! Обновление: теперь это реализовано благодаря Xeo!
  • Добавьте пространства имен, чтобы вспомогательные классы не попадали в глобальное пространство имен. Выполнено
  • Добавить псевдонимы шаблонов (или что-то подобное), чтобы облегчить создание пользовательских классов разделителей, или, возможно, макросы препроцессора?

Недавние обновления:

  • Я удалил пользовательский итератор вывода в пользу простого цикла for в функции print.
  • Все детали реализации теперь находятся в pretty_printпространстве имен. Только глобальные операторы потока и pretty_print_arrayоболочка находятся в глобальном пространстве имен.
  • Исправлено пространство имен, так что operator<<теперь оно правильно в std.

Ноты:

  • Удаление выходного итератора означает, что нет никакого способа использовать std::copy()симпатичную печать. Я мог бы восстановить симпатичный итератор, если это желаемая особенность, но приведенный ниже код Свена имеет реализацию.
  • Это было сознательное проектное решение сделать константы времени компиляции разделителями, а не константами объекта. Это означает, что вы не можете динамически предоставлять разделители во время выполнения, но это также означает, что нет ненужных накладных расходов. Конфигурация разделителя на основе объектов была предложена Деннисом Зиккефусом в комментарии к приведенному ниже коду Свена. При желании это может быть реализовано как альтернативная функция.
  • В настоящее время не очевидно, как настроить разделители вложенных контейнеров.
  • Помните, что целью этой библиотеки является предоставление возможности быстрой печати контейнера, которая требует нулевого кодирования с вашей стороны. Это не универсальная библиотека форматирования, а скорее инструмент разработки, позволяющий облегчить необходимость написания кода для проверки контейнера.

Спасибо всем, кто внес свой вклад!


Примечание. Если вы ищете быстрый способ развертывания пользовательских разделителей, вот один из способов использования стирания типов. Мы предполагаем, что вы уже создали класс разделителя, скажем MyDelтак:

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

Теперь мы хотим иметь возможность писать std::cout << MyPrinter(v) << std::endl;для некоторого контейнера, vиспользуя эти разделители. MyPrinterбудет классом стирания типа, вот так:

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

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

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

template <typename Delims>
std::ostream & operator<<(std::ostream & o, const MyPrinter<Delims> & p) { return p.base->stream(o); }
Керрек С.Б.
источник
Ваш код не будет работать. нет такого ключевого слова, как контейнер C
the_drow
31
@the_drow: Похоже, OP уже знает это. Они просто указывают, что они ищут.
Марсело Кантос
Действительно, я привел только «моральный» пример псевдокода. (Я также не указал тип возвращаемого значения.) Конечно, я даже не знаю, как лучше сделать разделители изменяемыми.
Керрек С.Б.
1
Другой альтернативой может быть помещение операторов в pretty_printпространство имен и предоставление оболочки для использования пользователем при печати. С точки зрения пользователя: std::cout << pretty_print(v);(возможно, с другим именем). Затем вы можете указать оператора в том же пространстве имен, что и обертка, и затем развернуть его, чтобы печатать что угодно. Вы также можете улучшить оболочку, позволяя при желании определить разделитель для использования в каждом вызове (вместо того, чтобы использовать черты, которые вызывают одинаковый выбор для всего приложения) \
David Rodríguez - dribeas
1
Пожалуйста, делайте ваши "обновленные" ответы на реальные ответы, вместо того, чтобы задавать вопросы.
einpoklum

Ответы:

82

Это решение было вдохновлено решением Марсело с несколькими изменениями:

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

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

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

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

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

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

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

#if _MSC_VER >= 1400

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

#endif // _MSC_VER >= 1400

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    stream << value.first;

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

    stream << value.second;

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

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

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

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

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

Как и версия Marcelo, она использует черту типа is_container, которая должна быть специализированной для всех поддерживаемых контейнеров. Может быть возможно использовать признак для проверки value_type, const_iterator, begin()/ end(), но я не уверен , я бы рекомендовал , так как это может соответствовать вещам , которые соответствуют этим критериям , но которые на самом деле не контейнеры, как std::basic_string. Также как и версия Marcelo, она использует шаблоны, которые могут быть специализированными для указания используемых разделителей.

Основное отличие состоит в том, что я построил свою версию вокруг a pretty_ostream_iterator, которая работает аналогично, std::ostream_iteratorно не печатает разделитель после последнего элемента. Форматирование контейнеров выполняется с помощью команды print_container_helper, которая может использоваться непосредственно для печати контейнеров без черты is_container или для указания другого типа разделителей.

Я также определил is_container и разделители, чтобы он работал для контейнеров с нестандартными предикатами или распределителями, а также для char и wchar_t. Функция operator << также определена для работы с потоками char и wchar_t.

Наконец, я использовал std::enable_if, который доступен как часть C ++ 0x и работает в Visual C ++ 2010 и g ++ 4.3 (требуется флаг -std = c ++ 0x) и позже. Таким образом, нет никакой зависимости от Boost.

Sven
источник
Если я читаю это право, для того, чтобы получить парную печать как <i, j>в одной функции, так и [i j]в другой, вам нужно определить целый новый тип с несколькими статическими членами, чтобы передать этот тип print_container_helper? Это кажется слишком сложным. Почему бы не перейти к реальному объекту, с полями, которые вы можете установить в каждом конкретном случае, а специализации просто предоставляют разные значения по умолчанию?
Деннис Зикефуз
Посмотрите на это так: если есть несколько разделителей, которые вам нравятся лично, вы можете создать пару классов со статическими членами раз и навсегда, а затем просто использовать их. Конечно, вы правы, что использование print_container_helperне так элегантно, как просто operator<<. Конечно, вы всегда можете изменить источник или просто добавить явную специализацию для вашего любимого контейнера, например, для pair<int, int>и для pair<double, string>. В конечном счете, все зависит от удобства. Предложения по улучшению приветствуются!
Kerrek SB
... и в продолжение этого, если вам уже нужна ситуативная печать одного и того же типа данных в разных форматах, вам, вероятно, все равно придется написать хотя бы одну маленькую оболочку. Это не настраиваемая библиотека форматирования, а библиотека разумных значений по умолчанию с нулевым усилием, которая волшебным образом позволяет вам печатать контейнеры без раздумий ... (Но если вам нужна более глобальная гибкость, мы могли бы, возможно, добавить несколько #macros, чтобы сделать значения по умолчанию легко манипулировать.)
Kerrek SB
Реальная проблема заключается в том, что, хотя я мог бы легко изменить print_container_helper для использования параметров для пользовательских разделителей, на самом деле нет никакого способа указать разделители для внутреннего контейнера (или пары), кроме как специализировать шаблон разделителей. Достичь этого было бы очень сложно.
Свен
Мне почти удается создать удобное решение с разделителем типов, используя стирание типов. Если у вас уже есть класс разделителя MyDels, то я могу сказать std::cout << CustomPrinter<MyDels>(x);. На данный момент я не могу сказать std::cout << CustomDelims<"{", ":", "}">(x);, потому что вы не можете иметь const char *аргументы шаблона. Решение сделать константу времени компиляции ограничителей накладывает некоторые ограничения на простоту использования, но я думаю, что оно того стоит.
Kerrek SB
22

Это было отредактировано несколько раз, и мы решили вызвать основной класс, который обёртывает коллекцию RangePrinter

Это должно работать автоматически с любой коллекцией после того, как вы написали одноразовый оператор << overload, за исключением того, что вам понадобится специальный файл для карт для печати пары, и вы можете захотеть настроить разделитель там.

Вы также можете использовать специальную функцию «print» для использования элемента вместо его непосредственного вывода. Немного похоже на алгоритмы STL, позволяющие передавать пользовательские предикаты. С map вы бы использовали его таким образом, с пользовательским принтером для std :: pair.

Ваш принтер по умолчанию просто выведет его в поток.

Хорошо, давайте работать на нестандартном принтере. Я изменю свой внешний класс на RangePrinter. Итак, у нас есть 2 итератора и несколько разделителей, но мы не настроили, как печатать фактические элементы.

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

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

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

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

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

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

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

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

};


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

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

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

    printer( os, *begin );

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

Теперь по умолчанию это будет работать для карт, пока оба типа ключа и значения могут быть напечатаны, и вы можете вставить свой собственный специальный принтер элементов, когда они не (как вы можете с любым другим типом), или если вы не хотите = как разделитель.

Я перемещаю свободную функцию, чтобы создать их до конца сейчас:

Свободная функция (версия итератора) будет выглядеть примерно так, и вы можете даже иметь значения по умолчанию:

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

Затем вы можете использовать его для std :: set by

 std::cout << outputFormatter( mySet );

Вы также можете написать версию с бесплатной функцией, которая использует пользовательский принтер и версию с двумя итераторами. В любом случае они разрешат параметры шаблона для вас, и вы сможете передавать их как временные.

Дойная корова
источник
Понимаю. Это похоже на идею Марсело Кантоса, не так ли? Я постараюсь превратить это в рабочий пример, спасибо!
Керрек С.Б.
Я считаю, что это решение намного чище, чем у Марсело, и оно предлагает такую ​​же гибкость. Мне нравится аспект, который нужно явно обернуть вывод в вызов функции. Чтобы быть действительно крутым, вы могли бы добавить поддержку для вывода ряда итераторов напрямую, так что я могу это сделать std::cout << outputFormatter(beginOfRange, endOfRange);.
Бьорн Поллекс,
1
@CashCow: есть одна проблема с этим решением, похоже, оно не работает с рекурсивными коллекциями (т.е. коллекциями коллекций). std::pairэто самый основной пример «внутренней коллекции».
Матье М.
Мне очень нравится этот ответ, так как он не имеет зависимостей и не должен знать о контейнерах, которые он поддерживает. Можем ли мы выяснить, может ли он легко обрабатывать std::maps и работает ли он для коллекций коллекций? Однако я склонен принять это как ответ. Надеюсь Марсело не против, его решение тоже работает.
Керрек С.Б.
@Matthieu M. Это зависит от того, как вы печатаете внутреннюю коллекцию. Если вы просто используете os << open << * iter << close, то у вас будут проблемы с ним, но если вы разрешите своему пользователю передавать пользовательский принтер, как я уже предлагал, вы можете распечатать все, что захотите.
CashCow
14

Вот рабочая библиотека, представленная как полная рабочая программа, которую я только что взломал:

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

#include <boost/utility/enable_if.hpp>

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

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

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

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

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

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

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

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

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

В настоящее время он работает только с vectorи set, но может быть настроен для работы с большинством контейнеров, просто расширяя IsContainerспециализации. Я не особо задумывался о том, является ли этот код минимальным, но я не могу сразу думать о чем-либо, что я мог бы исключить излишним.

РЕДАКТИРОВАТЬ: Просто для удовольствия, я включил версию, которая обрабатывает массивы. Мне пришлось исключить массивы символов, чтобы избежать дальнейших двусмысленностей; с ним все еще могут быть проблемы wchar_t[].

Марсело Кантос
источник
2
@Nawaz: Как я уже сказал, это только начало решения. Вы можете поддержать std::map<>либо специализацию оператора, либо определив operator<<для std::pair<>.
Марсело Кантос
Тем не менее, +1 за использование Delimsшаблона класса!
Наваз
@MC: О, хорошо. Это выглядит очень многообещающе! (Кстати, вам нужен тип возврата "std :: ostream &", я изначально об этом забыл.)
Kerrek SB
Хм, я получаю "неоднозначную перегрузку" при попытке этого на std :: vector <int> и std :: set <std :: string> ...
Kerrek SB
Да, я сейчас выясняю, как предотвратить неоднозначности, которые вызваны тем, что operator<<шаблон соответствует практически чему-либо.
Марсело Кантос
10

Вы можете форматировать контейнеры, а также диапазоны и кортежи, используя библиотеку {fmt} . Например:

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

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

печать

{1, 2, 3}

к stdout.

Отказ от ответственности : я автор {fmt}.

Витовт
источник
8

Код оказывался удобным в некоторых случаях, и я чувствую, что затраты на настройку очень низки, поскольку использование довольно низкое. Таким образом, я решил выпустить его под лицензией MIT и предоставить репозиторий GitHub, где можно загрузить заголовок и небольшой файл примера.

http://djmuw.github.io/prettycc

0. Предисловие и формулировка

A «украшение» с точкой зрения этого ответа является набором префикса струны, Разделитель-строка, и постфикс струны. Где строка префикса вставляется в поток до, а строка постфикса после значений контейнера (см. 2. Целевые контейнеры). Строка разделителя вставляется между значениями соответствующего контейнера.

Примечание. На самом деле, этот ответ не отвечает на вопрос до 100%, поскольку декорация не является строго скомпилированной постоянной времени, поскольку для проверки того, было ли применено нестандартное декорирование к текущему потоку, требуются проверки времени выполнения. Тем не менее, я думаю, что у этого есть некоторые приличные особенности.

Примечание 2: могут иметь незначительные ошибки, так как он еще не был хорошо протестирован.

1. Общая идея / использование

Нулевой дополнительный код, необходимый для использования

Это должно быть так же легко, как

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

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

Простая настройка ...

... по отношению к конкретному объекту потока

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

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

или относительно всех потоков:

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

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

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

Грубое описание

  • Код включает в себя шаблон класса, обеспечивающий оформление по умолчанию для любого типа
  • который может быть специализирован, чтобы изменить декорацию по умолчанию для (а) определенного типа (ов), и это
  • использование частного хранилища, предоставляемого с ios_baseпомощью xalloc/ pword, чтобы сохранить указатель на pretty::decorобъект, специально украшающий определенный тип в определенном потоке.

Если никакой pretty::decor<T>объект для этого потока не был установлен явно, pretty::defaulted<T, charT, chartraitT>::decoration()вызывается для получения декорации по умолчанию для данного типа. Класс pretty::defaultedдолжен быть специализированным для настройки декораций по умолчанию.

2. Целевые объекты / контейнеры

Целевые объекты objдля «красивого украшения» этого кода - это объекты, имеющие

  • перегружены std::beginи std::endопределены (включая массивы в стиле C),
  • наличие begin(obj)и end(obj)доступ через ADL,
  • имеют тип std::tuple
  • или типа std::pair.

Код включает в себя признак для идентификации классов с особенностями диапазона ( begin/ end). (Там нет проверки, begin(obj) == end(obj)является ли выражение допустимым, хотя.)

Код предоставляет operator<<s в глобальном пространстве имен, которые применяются только к классам, не имеющим более специализированной operator<<доступной версии . Поэтому, например std::string, не печатается с использованием оператора в этом коде, хотя имеет действительную begin/ endпару.

3. Использование и настройка

Декорации могут быть наложены отдельно для каждого типа (кроме разных tuple) и потока (не типа потока!). (То есть std::vector<int>может иметь разные декорации для разных потоковых объектов.)

А) Стандартное оформление

Префикс по умолчанию ""(ничего), как и постфикс по умолчанию, а разделитель по умолчанию ", "(запятая + пробел).

Б) Настраиваемое оформление по умолчанию для типа путем специализации pretty::defaultedшаблона класса.

struct defaultedИмеет статическую функцию - член , decoration()возвращающую decorобъект , который включает в себя значения по умолчанию для данного типа.

Пример использования массива:

Настройка печати по умолчанию:

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

Распечатать массив массива:

float e[5] = { 3.4f, 4.3f, 5.2f, 1.1f, 22.2f };
std::cout << e << '\n'; // prints (3.4:4.3:5.2:1.1:22.2)

Использование PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)макроса для charпотоков

Макрос расширяется до

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

позволяя переписать вышеуказанную частичную специализацию на

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

или вставив полную специализацию, как

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

Другой макрос для wchar_tпотоков входят: PRETTY_DEFAULT_WDECORATION.

В) накладывать украшения на ручьи

Функция pretty::decorationиспользуется для наложения декораций на определенный поток. Существуют перегрузки, принимающие либо один строковый аргумент, являющийся разделителем (принимающий префикс и постфикс из класса по умолчанию), либо три строковых аргумента, собирающих полное оформление

Полное украшение для данного типа и потока

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

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

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

Настройка разделителя для данного потока

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

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

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

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

4. Специальная обработка std::tuple

Вместо того, чтобы разрешать специализацию для каждого возможного типа кортежа, этот код применяет любое украшение, доступное для std::tuple<void*>всех видов std::tuple<...>s.

5. Удалить пользовательские украшения из потока

Чтобы вернуться к декорации по умолчанию для данного типа, используйте pretty::clearшаблон функции в потоке s.

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

5. Дополнительные примеры

Печать "в виде матрицы" с разделителем новой строки

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

Печать

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

Посмотреть это на ideone / KKUebZ

6. Код

#ifndef pretty_print_0x57547_sa4884X_0_1_h_guard_
#define pretty_print_0x57547_sa4884X_0_1_h_guard_

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

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

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

namespace pretty
{

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

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

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

  namespace detail
  {

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

    template<class T> struct clearer {};

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

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

  }

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

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

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

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

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

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

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

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

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

}

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

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

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


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

#endif // pretty_print_0x57547_sa4884X_0_1_h_guard_
Pixelchemist
источник
4

Я собираюсь добавить еще один ответ здесь, потому что я предложил другой подход к моему предыдущему, то есть использовать языковые аспекты.

Основы здесь

По сути, что вы делаете:

  1. Создайте класс, производный от std::locale::facet. Небольшой недостаток в том, что вам понадобится модуль компиляции где-нибудь для хранения его идентификатора. Давайте назовем это MyPrettyVectorPrinter. Вы, вероятно, дадите ему лучшее имя, а также создадите их для пары и карты.
  2. В своей функции потока вы проверяете std::has_facet< MyPrettyVectorPrinter >
  3. Если это возвращает true, распакуйте его std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. Ваши фасетные объекты будут иметь значения для разделителей, и вы можете их читать. Если фасет не найден, ваша функция печати ( operator<<) предоставляет функции по умолчанию. Обратите внимание, что вы можете сделать то же самое для чтения вектора.

Мне нравится этот метод, потому что вы можете использовать печать по умолчанию, но при этом можете использовать пользовательское переопределение.

Недостатками являются необходимость библиотеки для вашего аспекта, если она используется в нескольких проектах (поэтому не может быть только заголовками), а также тот факт, что вам необходимо остерегаться затрат на создание нового объекта локали.

Я написал это как новое решение, а не изменяю другое, потому что считаю, что оба подхода могут быть правильными, и вы выбираете.

Дойная корова
источник
Позвольте мне сказать прямо: при таком подходе мне нужно активно вносить в белый список каждый тип контейнера, который я хочу использовать?
Керрек С.Б.
На самом деле не следует расширять стандартное стандартное представление, кроме как для собственных типов, но вы пишете перегрузку оператора << для каждого типа контейнера (вектор, карта, список, deque) плюс пара, которую хотите печатать. Конечно, некоторые могут иметь общий фасет (например, вы можете все же распечатать список, вектор и дек). Вы предоставляете метод печати «по умолчанию», но разрешаете пользователям создавать фасет, локаль и наполнение перед печатью. Немного похоже на то, как boost печатает их date_time. Можно также загрузить их фасет в глобальную локаль для печати таким образом по умолчанию.
CashCow
2

Цель здесь - использовать ADL для настройки того, как мы красиво печатаем.

Вы передаете тег форматера и переопределяете 4 функции (до, после, между и спуском) в пространстве имен тега. Это меняет способ, которым форматтер печатает «украшения» при переборе контейнеров.

Средство форматирования по умолчанию, которое используется {(a->b),(c->d)}для карт, (a,b,c)для кортежей, "hello"для строк, [x,y,z]для всего остального, что включено.

Он должен «просто работать» со сторонними итеративными типами (и обращаться с ними как со «всем остальным»).

Если вы хотите, чтобы пользовательские украшения использовались для сторонних элементов, просто создайте свой собственный тег. Чтобы справиться с спуском карты, потребуется немного усилий ( pretty_print_descend( your_tagчтобы вернуться, нужно перегрузить pretty_print::decorator::map_magic_tag<your_tag>). Может быть, есть более чистый способ сделать это, не уверен.

Небольшая библиотека для определения итерации и кортежа:

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

Библиотека, которая позволяет нам посещать содержимое объекта типа итерация или кортеж:

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

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

Красивая библиотека печати:

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

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

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

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

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

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

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

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

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

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

Тестовый код:

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

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

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

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

живой пример

При этом используются функции C ++ 14 (некоторые _tпсевдонимы и auto&&лямбды), но ни одна из них не обязательна.

Якк - Адам Невраумонт
источник
@KerrekSB рабочая версия, с некоторыми изменениями. Основная часть кода - это "посещение кортежей / итераций" и необычное форматирование (в том числе в ->пределах pairs map) на этом этапе. Ядро симпатичной библиотеки печати красивое и маленькое, что приятно. Я пытался сделать его легко расширяемым, не уверен, что мне это удастся.
Якк - Адам Невраумонт
1

Мое решение - просто .h , который является частью пакета scc . Все стандартные контейнеры, карты, наборы, c-массивы можно распечатать.

Леонид Вольницкий
источник
Интересно. Мне нравится подход шаблон-шаблон для контейнеров, но работает ли он для пользовательских контейнеров и контейнеров STL с нестандартными предикатами или распределителями? (Я сделал нечто подобное для попытки реализовать bimap в C ++ 0x с использованием шаблонов с переменным числом аргументов.) Кроме того, вы, похоже, не используете итераторы для своих процедур печати; почему явное использование счетчика i?
Kerrek SB
Что такое контейнер с нестандартными предикатами? Пользовательский контейнер, который соответствует подписи, будет напечатан. Нестандартные распределители сейчас не поддерживаются, но это легко исправить. Мне просто не нужно это сейчас.
Леонид Вольницкий
Нет веской причины использовать индекс вместо итераторов. Исторические причины. Исправлю, когда у меня будет время.
Леонид Вольницкий
Под «контейнером с нестандартными предикатами» я подразумеваю что-то вроде std::setпользовательского компаратора или unordered_map с пользовательским равенством. Было бы очень важно поддержать эти конструкции.
Керрек С.Б.
1

Выходя из одного из первых BoostCon (теперь он называется CppCon), я и двое других работали над библиотекой, чтобы сделать именно это. Основным камнем преткновения было расширение пространства имен std. Это оказалось бесполезным для библиотеки повышения.

К сожалению, ссылки на код больше не работают, но вы можете найти некоторые интересные моменты в обсуждениях (по крайней мере, те, которые не говорят о том, как его назвать!)

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

Джеффри Фауст
источник
0

Вот моя версия реализации, сделанная в 2016 году

Все в одном заголовке, поэтому его легко использовать 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;
    }
}
Skident
источник