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

326

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

Обновление: я создал публичный проект для 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, которое может выяснить, есть ли что-то нужный итератор?

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


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

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

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

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

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

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

#ifndef H_PRETTY_PRINT
#define H_PRETTY_PRINT


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


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

namespace pretty_print
{

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

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


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

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


    // Holds the delimiter values for a specific character type

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


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

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


    // Default delimiters

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


    // Delimiters for set

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


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

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


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

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

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

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

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

                stream << *it;
            }

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

    private:
        const T & _container;
    };


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

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

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

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

    private:
        const T & t;
    };

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

} // namespace pretty_print


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


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

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


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

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

    // Prints a container to the stream using default delimiters

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

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

        stream << value.first;

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

        stream << value.second;

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

        return stream;
    }
} // namespace std

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

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

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

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

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

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

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


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

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

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

        return stream;
    }
} // namespace std


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

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

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

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

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


#endif

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

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

#include "prettyprint.h"

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

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

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

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

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

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

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

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

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

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

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

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

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

Дополнительные идеи для улучшения:

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

Последние обновления:

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

Примечания:

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

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


Примечание. Если вы ищете быстрый способ развертывания пользовательских разделителей, вот один из способов использования стирания стилей. Предположим, что вы уже создали класс разделителя, скажем 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); }
  • 0
    Ваш код не будет работать. нет такого ключевого слова как контейнер C
  • 28
    @the_drow: Похоже, OP уже знает это. Они просто указывают, что они ищут.
Показать ещё 33 комментария
Теги:
c++11
templates
operator-overloading
pretty-print

7 ответов

76
Лучший ответ

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

#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(), но я не уверен, что рекомендую это, поскольку он может соответствовать тем, которые соответствуют этим критериям, но aren ' t на самом деле контейнеры, например std::basic_string. Также как и версия Marcelo, она использует шаблоны, которые могут быть специализированы для указания разделителей.

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

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

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

  • 0
    Если я читаю это право, для того, чтобы пара печатала как <i, j> в одной функции и как [ij] в другой, вам нужно определить целый новый тип с несколькими статическими членами, чтобы передать этот тип в print_container_helper ? Это кажется слишком сложным. Почему бы не пойти с реальным объектом, с полями, которые вы можете установить в каждом конкретном случае, а специализации просто предоставляют различные значения по умолчанию?
  • 0
    Посмотрите на это следующим образом: если есть несколько разделителей, которые вам нравятся лично, вы можете создать пару классов со статическими членами раз и навсегда, а затем просто использовать их. Конечно, вы правы, что использование print_container_helper не так элегантно, как просто operator<< . Конечно, вы всегда можете изменить источник или просто добавить явные специализации для вашего любимого контейнера, например, для pair<int, int> и для pair<double, string> . В конечном счете, все зависит от удобства и удобства. Предложения по улучшению приветствуются!
Показать ещё 9 комментариев
20

Это было отредактировано несколько раз, и мы решили вызвать основной класс, который обертывает коллекцию 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 с помощью

 std::cout << outputFormatter( mySet );

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

  • 0
    Понимаю. Это похоже на идею Марсело Кантоса, не так ли? Я постараюсь превратить это в рабочий пример, спасибо!
  • 0
    Я считаю, что это решение намного чище, чем у Марсело, и предлагает такую же гибкость. Мне нравится аспект, который нужно явно обернуть вывод в вызов функции. Чтобы быть действительно крутым, вы могли бы добавить поддержку для вывода ряда итераторов напрямую, чтобы я мог сделать std::cout << outputFormatter(beginOfRange, endOfRange); ,
Показать ещё 16 комментариев
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: Только для ударов я включил версию, которая обрабатывает массивы. Я должен был исключить массивы char, чтобы избежать дальнейших двусмысленностей; он может столкнуться с проблемой wchar_t[].

  • 0
    @Marcelo: это будет работать с std::map ? Я думаю нет!
  • 2
    @Nawaz: Как я уже сказал, это только начало решения. Вы можете поддерживать std::map<> либо специализируя оператор, либо определяя operator<< для std::pair<> .
Показать ещё 7 комментариев
7

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

http://djmuw.github.io/prettycc

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

"Декорация" в терминах этого ответа представляет собой набор префиксной строки, разделительной строки и постфиксной строки. Если строка префикса вставляется в поток до и после постфиксной строки после значений контейнера (см. 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<< в глобальном пространстве имен, который применяется только к классам, не имеющим более специализированной версии operator<<. Поэтому, например, std::string не печатается с использованием оператора в этом коде, хотя имеет действительную пару begin/end.

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

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

A) Украшение по умолчанию

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

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{ { "(" }, { ":" }, { ")" } };
    }
  };
}

Печать массива arry:

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.

C) Накладывать украшения на потоки

Функция 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

Смотрите на идеон /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_
5

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

Основы здесь

По существу, вы делаете следующее:

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

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

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

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

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

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

  • 0
    Интересно. Мне нравится подход шаблон-шаблон для контейнеров, но работает ли он для пользовательских контейнеров и контейнеров STL с нестандартными предикатами или распределителями? (Я сделал нечто подобное для попытки реализовать bimap в C ++ 0x с использованием шаблонов с переменным числом аргументов.) Кроме того, вы, похоже, не используете итераторы для своих процедур печати; почему явное использование счетчика i ?
  • 0
    Что такое контейнер с нестандартными предикатами? Пользовательский контейнер, который соответствует подписи, будет напечатан. Нестандартные распределители сейчас не поддерживаются, но это легко исправить. Мне просто не нужно это сейчас.
Показать ещё 2 комментария
1

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

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

Форматировщик по умолчанию, который делает {(a->b),(c->d)} для карт, (a,b,c) для tupleoids, "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";
}

живой пример

Это использует функции С++ 14 (некоторые псевдонимы _t и auto&& lambdas), но ни один из них не является существенным.

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню