Симпатичні друковані контейнери C ++ STL


389

Зверніть увагу на оновлення в кінці цієї публікації.

Оновлення: Я створив публічний проект на GitHub для цієї бібліотеки!


Я хотів би мати єдиний шаблон, який раз і назавжди піклується про гарне надрукування всіх контейнерів STL через operator<<. У псевдокоді я шукаю щось подібне:

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

Тепер я бачив тут багато магічних шаблонів, так що я ніколи не вважав можливим, тому мені цікаво, чи хтось може запропонувати щось, що відповідатиме всім контейнерам C. Можливо, щось таке, що може визначити, якщо щось має необхідний ітератор ?

Дуже дякую!


Оновлення (та рішення)

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

Помічник "wrap_array ()" може використовуватися для друку необроблених масивів C. Оновлення: пари та кортежі доступні для друку; Розмежувачі за замовчуванням - круглі дужки.

Для ознаки типу 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>. Оновлення: Це тепер окреме питання про ТАК ! Upupdate: Це тепер було реалізовано, завдяки Xeo!
  • Додайте простори імен, щоб допоміжні класи не кровоточили у глобальний простір імен. Зроблено
  • Додайте псевдоніми шаблонів (чи щось подібне), щоб полегшити створення спеціальних класів роздільника, чи, можливо, макроси попереднього процесу?

Останні оновлення:

  • Я видалив користувальницький ітератор виводу на користь простого циклу для функції друку.
  • Усі деталі реалізації тепер знаходяться в 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: Схоже, ОП це вже знає. Вони просто вказують, що шукають.
Марсело Кантос

Дійсно, я лише наводив "моральний" приклад псевдокоду. (Я також відмовився від типу повернення, зауважую.) Напевно, я навіть не знаю, як краще зробити роздільники змінними.
Керрек СБ

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

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

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

Я також визначив is_container та роздільники, тому він буде працювати для контейнерів з нестандартними предикатами чи алокаторами, а також для char та wchar_t. Сама функція << оператор також визначена для роботи з потоками char та wchar_t.

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


Якщо я читаю це право, щоб мати пару друку як <i, j>в одній функції, так і [i j]в іншій, вам потрібно визначити цілий новий тип із жменькою статичних членів, щоб передати цей тип print_container_helper? Це здається надмірно складним. Чому б не перейти з фактичним об'єктом, з полями, які можна встановити в кожному конкретному випадку, а спеціалізації просто надають різні значення за замовчуванням?
Dennis Zickefoose

Дивіться це так: Якщо є купа роздільників, які вам подобаються особисто, ви можете зробити пару занять зі статичними членами раз і назавжди, а потім просто використовувати їх. Звичайно, ви праві, що використовувати print_container_helperне так елегантно, як просто operator<<. Ви завжди можете змінити джерело або просто додати явні спеціалізації для вашого улюбленого контейнера, наприклад, для pair<int, int>та для pair<double, string>. Зрештою, це питання зважування потужності на зручність. Пропозиції щодо вдосконалення ласкаво просимо!
Керрек СБ

... і щоб продовжити це, якщо вам вже потрібна ситуаційна друк одного типу даних у різних форматуваннях, вам, мабуть, доведеться написати хоча б одну невелику обгортку. Це не дуже налаштована бібліотека форматування, а скоріше нульова зусилля бібліотеки розумних за замовчуванням, яка магічно дозволяє друкувати контейнери, не замислюючись ... (Але якщо ви хочете отримати більшу глобальну гнучкість, ми могли б, можливо, додати трохи #macros, щоб зробити за замовчуванням легко маніпулювати.)
Kerrek SB

Справжня проблема полягає в тому, що, хоча я міг легко змінити print_container_helper, щоб використовувати параметри для спеціальних роздільників, насправді немає жодного способу вказати роздільники для внутрішнього контейнера (або пари), крім спеціалізованого шаблону роздільників. Досягти цього було б дуже складно.
Свен

Мені майже вдається досягти зручного рішення для розділення волосся за допомогою типу стирання. Якщо у вас вже є розділовий клас MyDels, то можу сказати std::cout << CustomPrinter<MyDels>(x);. Що я не можу зробити на даний момент, це сказати std::cout << CustomDelims<"{", ":", "}">(x);, тому що ви не можете мати const char *аргументи шаблону. Рішення зробити обмежувачі часу компілятора обмежувачами часу встановлює деякі обмеження щодо простоти використання, але я вважаю, що це варто того.
Керрек СБ

22

Це було відредаговано декілька разів, і ми вирішили назвати основний клас, який охоплює колекцію RangePrinter

Це повинно працювати автоматично з будь-якою колекцією після того, як ви записали одноразове перевантаження << оператора <<, за винятком того, що вам знадобиться спеціальний для карт для друку пари і, можливо, потрібно налаштувати роздільник.

Ви також можете мати спеціальну функцію «друку», яку слід використовувати на елементі, а не просто виводити його безпосередньо. Трохи на зразок алгоритмів STL дозволяють переходити у власні предикати. З картою ви б використали це так, за допомогою спеціального принтера для 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::mapсистемою та чи працює вона для колекцій колекцій? Я хотів би прийняти це як відповідь. Я сподіваюся, що Марсело не проти, його рішення також працює.
Керрек СБ

@Matthieu M. Це залежить від того, як ви надрукуєте внутрішню колекцію. Якщо ви просто використовуєте os << відкрити << * iter << закрити, у вас виникне проблема з цим, але якщо ви дозволите своєму користувачеві переходити на користувальницький принтер, як я запропонував, ви можете надрукувати все, що завгодно.
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спеціалізації. Я не надто замислювався над тим, чи мінімальний цей код, але я не можу одразу придумати щось, що могло б викреслити як зайве.

EDIT: Тільки для ударів я включив версію, яка обробляє масиви. Мені довелося виключати масиви знаків, щоб уникнути подальших неясностей; це все ще може зіткнутися з проблемою wchar_t[].


2
@Nawaz: Як я вже сказав, це лише початок рішення. Ви можете підтримати std::map<>або спеціалізуючись на операторі, або визначивши operator<<для std::pair<>.
Марсело Кантос

Однак +1 для використання Delimsшаблону класу!
Наваз

@MC: Добре. Це виглядає дуже перспективно! (До речі, вам потрібен тип повернення "std :: ostream &", я це забув спочатку.)
Керрек СБ

Хм, я отримую "неоднозначне перевантаження", намагаючись це на 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;
}

Приблизний опис

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

Якщо жоден pretty::decor<T>об'єкт для цього потоку не встановлено явно pretty::defaulted<T, charT, chartraitT>::decoration(), викликується отримати декор за замовчуванням для даного типу. Клас pretty::defaultedповинен бути спеціалізованим для налаштування прикрас за замовчуванням.

2. Цільові предмети / контейнери

Цільовими об’єктами objдля «гарного декору» цього коду є об’єкти, що мають будь-який

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

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

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

3. Використання та налаштування

Прикраси можна накладати окремо для кожного типу (крім різних tuples) та потоку (не типу потоку!). (Тобто, std::vector<int>може бути різне прикраса для різних об'єктів потоку.)

А) Оформлення за замовчуванням

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

B) Індивідуальне оформлення за замовчуванням типу за допомогою спеціалізації pretty::defaultedшаблону класу

Функція struct defaultedмає статичний член, що decoration()повертає decorоб'єкт, який включає значення за замовчуванням для даного типу.

Приклад використання масиву:

Налаштувати друк масиву за замовчуванням:

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

Вивести масив масиву:

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_

4

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

Основи тут

По суті, ти робиш:

  1. Створіть клас, який походить від std::locale::facet. Невеликий мінус полягає в тому, що десь вам знадобиться компіляційний блок, щоб зберігати його ідентифікатор. Назвемо це MyPrettyVectorPrinter. Ви, напевно, дасте йому кращу назву, а також створите для пари та карти.
  2. У функції потоку ви перевіряєте std::has_facet< MyPrettyVectorPrinter >
  3. Якщо це повертає істину, витягніть її std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. Ваші гранітні об’єкти матимуть значення для роздільників, і ви можете їх читати. Якщо фасета не знайдена, функція друку ( operator<<) надає типові функції. Зауважте, що ви можете зробити те саме, що читати вектор.

Мені подобається цей метод, тому що ви можете використовувати друк за замовчуванням, в той час як ви все ще можете використовувати спеціальний переопределення.

Мінуси потребують бібліотеки для вашої сторони, якщо вона використовується в декількох проектах (тому це не може бути лише заголовками), а також той факт, що вам потрібно остерігатися витрат на створення нового об'єкта локалі.

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


Дозвольте мені зрозуміти це: при такому підході мені потрібно активно додати білий список кожного типу контейнера, який я хочу використовувати?
Керрек СБ

Ну дійсно, не слід поширювати std, крім інших власних типів, але ви пишете перевантаження оператора << для кожного типу контейнера (вектор, карта, список, deque) плюс пара, яку ви хочете мати можливість надрукувати. Звичайно, дехто може поділитись аспектом (наприклад, ви можете надрукувати список, векторний і декетовий все одно). Ви надаєте метод друку "за замовчуванням", але дозволяєте користувачам створювати грань і локал та набухатись перед друком. Трохи схожий на спосіб збільшення друкує їх 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 робоча версія, з деякими змінами. Основна частина коду є загальним «візит кортежі / ітеріруемимі», і фантазії форматування ( в тому числі в ->межах pairх від mapх) в цій точці. Ядро гарненької друкованої бібліотеки є приємним і маленьким, що приємно. Я намагався зробити його легко розширюваним, не впевнений, чи вдалося мені.
Якк - Адам Невраумон

1

Моє рішення simple.h , яке входить до пакету scc . Усі контейнери std, карти, набори, c-масиви друкуються.


Цікаво. Мені подобається підхід шаблонів для контейнерів, але чи працює він для спеціальних контейнерів та контейнерів STL з нестандартними предикатами або розподільниками? (Я зробив щось подібне для спроби реалізації бімапи в C ++ 0x за допомогою шаблонів різних варіантів .) Крім того, ви, схоже, не використовуєте ітераторів загально для своїх друкованих процедур; чому явне використання лічильника i?
Керрек СБ

Що таке контейнер з нестандартними предикатами? Спеціальний контейнер, який відповідає підпису, буде надрукований. Зараз не підтримуються нестандартні алокатори, але це легко виправити. Мені це просто зараз не потрібно.
Леонід Вольницький,

Немає вагомих причин використовувати індекс замість ітераторів. Історичні причини. Виправляю, коли встигну.
Леонід Вольницький

Під "контейнером з нестандартними предикатами" я маю на увазі щось на зразок a std::setіз користувацьким компаратором або unororder_map із користувацькою рівністю. Було б дуже важливо підтримати ці споруди.
Керрек СБ

1

Вийшовши з одного з перших BoostCon (зараз його називають CppCon), я та двоє інших працювали над бібліотекою, щоб зробити саме це. Основним моментом в тому, що потрібно було розширити простір імен std. Це виявилося непотрібним для розширення бібліотеки.

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

http://boost.2283326.n4.nabble.com/explore-Library-Proposed-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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.