Пожалуйста, обратите внимание на обновления в конце этого поста.
Обновление: я создал публичный проект на GitHub для этой библиотеки!
Я хотел бы иметь один шаблон, который раз и навсегда позаботится о красивой печати через все контейнеры STL operator<<
. В псевдокоде я ищу что-то вроде этого:
template<container C, class T, String delim = ", ", String open = "[", String close = "]">
std::ostream & operator<<(std::ostream & o, const C<T> & x)
{
o << open;
// for (typename C::const_iterator i = x.begin(); i != x.end(); i++) /* Old-school */
for (auto i = x.begin(); i != x.end(); i++)
{
if (i != x.begin()) o << delim;
o << *i;
}
o << close;
return o;
}
Теперь я видел много магии шаблонов здесь на SO, что я никогда не думал, что это возможно, поэтому мне интересно, если кто-нибудь может предложить что-то, что будет соответствовать всем контейнерам C. Может быть, что-то trait-ish, который может выяснить, если у чего-то есть необходимый итератор ?
Большое спасибо!
Обновление (и решение)
После того , как я снова поднял эту проблему на 9-м канале , я получил фантастический ответ от Свена Гроота, который в сочетании с небольшим количеством признаков типа SFINAE, кажется, решает проблему полностью общим и нестабильным образом. Разделители могут быть индивидуально специализированными, приведена примерная специализация для std :: set, а также пример использования пользовательских разделителей.
Помощник "wrap_array ()" может быть использован для печати необработанных массивов Си. Обновление: Пары и кортежи доступны для печати; разделителями по умолчанию являются круглые скобки.
Черта типа enable-if требует C ++ 0x, но с некоторыми изменениями должна быть возможность сделать версию C ++ 98 этого. Кортежи требуют шаблонов с переменным числом, следовательно, C ++ 0x.
Я попросил Свена опубликовать здесь решение, чтобы я мог принять его, но пока я хотел бы опубликовать код самостоятельно для справки. ( Обновление: Свен теперь разместил свой код ниже, на который я сделал принятый ответ. Мой собственный код использует черты контейнерного типа, которые работают для меня, но могут вызывать неожиданное поведение с неконтейнерными классами, которые предоставляют итераторы.)
Заголовок (prettyprint.h):
#ifndef H_PRETTY_PRINT
#define H_PRETTY_PRINT
#include <type_traits>
#include <iostream>
#include <utility>
#include <tuple>
namespace std
{
// Pre-declarations of container types so we don't actually have to include the relevant headers if not needed, speeding up compilation time.
template<typename T, typename TTraits, typename TAllocator> class set;
}
namespace pretty_print
{
// SFINAE type trait to detect a container based on whether T::const_iterator exists.
// (Improvement idea: check also if begin()/end() exist.)
template<typename T>
struct is_container_helper
{
private:
template<typename C> static char test(typename C::const_iterator*);
template<typename C> static int test(...);
public:
static const bool value = sizeof(test<T>(0)) == sizeof(char);
};
// Basic is_container template; specialize to derive from std::true_type for all desired container types
template<typename T> struct is_container : public ::std::integral_constant<bool, is_container_helper<T>::value> { };
// Holds the delimiter values for a specific character type
template<typename TChar>
struct delimiters_values
{
typedef TChar char_type;
const TChar * prefix;
const TChar * delimiter;
const TChar * postfix;
};
// Defines the delimiter values for a specific container and character type
template<typename T, typename TChar>
struct delimiters
{
typedef delimiters_values<TChar> type;
static const type values;
};
// Default delimiters
template<typename T> struct delimiters<T, char> { static const delimiters_values<char> values; };
template<typename T> const delimiters_values<char> delimiters<T, char>::values = { "[", ", ", "]" };
template<typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"[", L", ", L"]" };
// Delimiters for set
template<typename T, typename TTraits, typename TAllocator> struct delimiters< ::std::set<T, TTraits, TAllocator>, char> { static const delimiters_values<char> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<char> delimiters< ::std::set<T, TTraits, TAllocator>, char>::values = { "{", ", ", "}" };
template<typename T, typename TTraits, typename TAllocator> struct delimiters< ::std::set<T, TTraits, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<wchar_t> delimiters< ::std::set<T, TTraits, TAllocator>, wchar_t>::values = { L"{", L", ", L"}" };
// Delimiters for pair (reused for tuple, see below)
template<typename T1, typename T2> struct delimiters< ::std::pair<T1, T2>, char> { static const delimiters_values<char> values; };
template<typename T1, typename T2> const delimiters_values<char> delimiters< ::std::pair<T1, T2>, char>::values = { "(", ", ", ")" };
template<typename T1, typename T2> struct delimiters< ::std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T1, typename T2> const delimiters_values<wchar_t> delimiters< ::std::pair<T1, T2>, wchar_t>::values = { L"(", L", ", L")" };
// Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type.
template<typename T, typename TChar = char, typename TCharTraits = ::std::char_traits<TChar>, typename TDelimiters = delimiters<T, TChar>>
struct print_container_helper
{
typedef TChar char_type;
typedef TDelimiters delimiters_type;
typedef std::basic_ostream<TChar, TCharTraits> & ostream_type;
print_container_helper(const T & container)
: _container(container)
{
}
inline void operator()(ostream_type & stream) const
{
if (delimiters_type::values.prefix != NULL)
stream << delimiters_type::values.prefix;
for (typename T::const_iterator beg = _container.begin(), end = _container.end(), it = beg; it != end; ++it)
{
if (it != beg && delimiters_type::values.delimiter != NULL)
stream << delimiters_type::values.delimiter;
stream << *it;
}
if (delimiters_type::values.postfix != NULL)
stream << delimiters_type::values.postfix;
}
private:
const T & _container;
};
// Type-erasing helper class for easy use of custom delimiters.
// Requires TCharTraits = std::char_traits<TChar> and TChar = char or wchar_t, and MyDelims needs to be defined for TChar.
// Usage: "cout << pretty_print::custom_delims<MyDelims>(x)".
struct custom_delims_base
{
virtual ~custom_delims_base() { }
virtual ::std::ostream & stream(::std::ostream &) = 0;
virtual ::std::wostream & stream(::std::wostream &) = 0;
};
template <typename T, typename Delims>
struct custom_delims_wrapper : public custom_delims_base
{
custom_delims_wrapper(const T & t) : t(t) { }
::std::ostream & stream(::std::ostream & stream)
{
return stream << ::pretty_print::print_container_helper<T, char, ::std::char_traits<char>, Delims>(t);
}
::std::wostream & stream(::std::wostream & stream)
{
return stream << ::pretty_print::print_container_helper<T, wchar_t, ::std::char_traits<wchar_t>, Delims>(t);
}
private:
const T & t;
};
template <typename Delims>
struct custom_delims
{
template <typename Container> custom_delims(const Container & c) : base(new custom_delims_wrapper<Container, Delims>(c)) { }
~custom_delims() { delete base; }
custom_delims_base * base;
};
} // namespace pretty_print
template <typename TChar, typename TCharTraits, typename Delims>
inline std::basic_ostream<TChar, TCharTraits> & operator<<(std::basic_ostream<TChar, TCharTraits> & stream, const pretty_print::custom_delims<Delims> & p)
{
return p.base->stream(stream);
}
// Template aliases for char and wchar_t delimiters
// Enable these if you have compiler support
//
// Implement as "template<T, C, A> const sdelims::type sdelims<std::set<T,C,A>>::values = { ... }."
//template<typename T> using pp_sdelims = pretty_print::delimiters<T, char>;
//template<typename T> using pp_wsdelims = pretty_print::delimiters<T, wchar_t>;
namespace std
{
// Prints a print_container_helper to the specified stream.
template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream,
const ::pretty_print::print_container_helper<T, TChar, TCharTraits, TDelimiters> & helper)
{
helper(stream);
return stream;
}
// Prints a container to the stream using default delimiters
template<typename T, typename TChar, typename TCharTraits>
inline typename enable_if< ::pretty_print::is_container<T>::value, basic_ostream<TChar, TCharTraits>&>::type
operator<<(basic_ostream<TChar, TCharTraits> & stream, const T & container)
{
return stream << ::pretty_print::print_container_helper<T, TChar, TCharTraits>(container);
}
// Prints a pair to the stream using delimiters from delimiters<std::pair<T1, T2>>.
template<typename T1, typename T2, typename TChar, typename TCharTraits>
inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const pair<T1, T2> & value)
{
if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.prefix != NULL)
stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.prefix;
stream << value.first;
if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.delimiter != NULL)
stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.delimiter;
stream << value.second;
if (::pretty_print::delimiters<pair<T1, T2>, TChar>::values.postfix != NULL)
stream << ::pretty_print::delimiters<pair<T1, T2>, TChar>::values.postfix;
return stream;
}
} // namespace std
// Prints a tuple to the stream using delimiters from delimiters<std::pair<tuple_dummy_t, tuple_dummy_t>>.
namespace pretty_print
{
struct tuple_dummy_t { }; // Just if you want special delimiters for tuples.
typedef std::pair<tuple_dummy_t, tuple_dummy_t> tuple_dummy_pair;
template<typename Tuple, size_t N, typename TChar, typename TCharTraits>
struct pretty_tuple_helper
{
static inline void print(::std::basic_ostream<TChar, TCharTraits> & stream, const Tuple & value)
{
pretty_tuple_helper<Tuple, N - 1, TChar, TCharTraits>::print(stream, value);
if (delimiters<tuple_dummy_pair, TChar>::values.delimiter != NULL)
stream << delimiters<tuple_dummy_pair, TChar>::values.delimiter;
stream << std::get<N - 1>(value);
}
};
template<typename Tuple, typename TChar, typename TCharTraits>
struct pretty_tuple_helper<Tuple, 1, TChar, TCharTraits>
{
static inline void print(::std::basic_ostream<TChar, TCharTraits> & stream, const Tuple & value) { stream << ::std::get<0>(value); }
};
} // namespace pretty_print
namespace std
{
template<typename TChar, typename TCharTraits, typename ...Args>
inline basic_ostream<TChar, TCharTraits> & operator<<(basic_ostream<TChar, TCharTraits> & stream, const tuple<Args...> & value)
{
if (::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.prefix != NULL)
stream << ::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.prefix;
::pretty_print::pretty_tuple_helper<const tuple<Args...> &, sizeof...(Args), TChar, TCharTraits>::print(stream, value);
if (::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.postfix != NULL)
stream << ::pretty_print::delimiters< ::pretty_print::tuple_dummy_pair, TChar>::values.postfix;
return stream;
}
} // namespace std
// A wrapper for raw C-style arrays. Usage: int arr[] = { 1, 2, 4, 8, 16 }; std::cout << wrap_array(arr) << ...
namespace pretty_print
{
template <typename T, size_t N>
struct array_wrapper
{
typedef const T * const_iterator;
typedef T value_type;
array_wrapper(const T (& a)[N]) : _array(a) { }
inline const_iterator begin() const { return _array; }
inline const_iterator end() const { return _array + N; }
private:
const T * const _array;
};
} // namespace pretty_print
template <typename T, size_t N>
inline pretty_print::array_wrapper<T, N> pretty_print_array(const T (& a)[N])
{
return pretty_print::array_wrapper<T, N>(a);
}
#endif
Пример использования:
#include <iostream>
#include <vector>
#include <unordered_map>
#include <map>
#include <set>
#include <array>
#include <tuple>
#include <utility>
#include <string>
#include "prettyprint.h"
// Specialization for a particular container
template<> const pretty_print::delimiters_values<char> pretty_print::delimiters<std::vector<double>, char>::values = { "|| ", " : ", " ||" };
// Custom delimiters for one-off use
struct MyDel { static const delimiters_values<char> values; };
const delimiters_values<char> MyDel::values = { "<", "; ", ">" };
int main(int argc, char * argv[])
{
std::string cs;
std::unordered_map<int, std::string> um;
std::map<int, std::string> om;
std::set<std::string> ss;
std::vector<std::string> v;
std::vector<std::vector<std::string>> vv;
std::vector<std::pair<int, std::string>> vp;
std::vector<double> vd;
v.reserve(argc - 1);
vv.reserve(argc - 1);
vp.reserve(argc - 1);
vd.reserve(argc - 1);
std::cout << "Printing pairs." << std::endl;
while (--argc)
{
std::string s(argv[argc]);
std::pair<int, std::string> p(argc, s);
um[argc] = s;
om[argc] = s;
v.push_back(s);
vv.push_back(v);
vp.push_back(p);
vd.push_back(1./double(i));
ss.insert(s);
cs += s;
std::cout << " " << p << std::endl;
}
std::array<char, 5> a{{ 'h', 'e', 'l', 'l', 'o' }};
std::cout << "Vector: " << v << std::endl
<< "Incremental vector: " << vv << std::endl
<< "Another vector: " << vd << std::endl
<< "Pairs: " << vp << std::endl
<< "Set: " << ss << std::endl
<< "OMap: " << om << std::endl
<< "UMap: " << um << std::endl
<< "String: " << cs << std::endl
<< "Array: " << a << std::endl
;
// Using custom delimiters manually:
std::cout << pretty_print::print_container_helper<std::vector<std::string>, char, std::char_traits<char>, MyDel>(v) << std::endl;
// Using custom delimiters with the type-erasing helper class
std::cout << pretty_print::custom_delims<MyDel>(v) << std::endl;
// Pairs and tuples and arrays:
auto a1 = std::make_pair(std::string("Jello"), 9);
auto a2 = std::make_tuple(1729);
auto a3 = std::make_tuple("Qrgh", a1, 11);
auto a4 = std::make_tuple(1729, 2875, std::pair<double, std::string>(1.5, "meow"));
int arr[] = { 1, 4, 9, 16 };
std::cout << "C array: " << wrap_array(arr) << std::endl
<< "Pair: " << a1 << std::endl
<< "1-tuple: " << a2 << std::endl
<< "n-tuple: " << a3 << std::endl
<< "n-tuple: " << a4 << std::endl
;
}
Дальнейшие идеи по улучшению:
Реализуйте вывод дляОбновление: теперь это отдельный вопрос о SO ! Обновление: теперь это реализовано благодаря Xeo!std::tuple<...>
таким же образом, как у насstd::pair<S,T>
.Добавьте пространства имен, чтобы вспомогательные классы не попадали в глобальное пространство имен.Выполнено- Добавить псевдонимы шаблонов (или что-то подобное), чтобы облегчить создание пользовательских классов разделителей, или, возможно, макросы препроцессора?
Недавние обновления:
- Я удалил пользовательский итератор вывода в пользу простого цикла for в функции print.
- Все детали реализации теперь находятся в
pretty_print
пространстве имен. Только глобальные операторы потока иpretty_print_array
оболочка находятся в глобальном пространстве имен. - Исправлено пространство имен, так что
operator<<
теперь оно правильно вstd
.
Ноты:
- Удаление выходного итератора означает, что нет никакого способа использовать
std::copy()
симпатичную печать. Я мог бы восстановить симпатичный итератор, если это желаемая особенность, но приведенный ниже код Свена имеет реализацию. - Это было сознательное проектное решение сделать константы времени компиляции разделителями, а не константами объекта. Это означает, что вы не можете динамически предоставлять разделители во время выполнения, но это также означает, что нет ненужных накладных расходов. Конфигурация разделителя на основе объектов была предложена Деннисом Зиккефусом в комментарии к приведенному ниже коду Свена. При желании это может быть реализовано как альтернативная функция.
- В настоящее время не очевидно, как настроить разделители вложенных контейнеров.
- Помните, что целью этой библиотеки является предоставление возможности быстрой печати контейнера, которая требует нулевого кодирования с вашей стороны. Это не универсальная библиотека форматирования, а скорее инструмент разработки, позволяющий облегчить необходимость написания кода для проверки контейнера.
Спасибо всем, кто внес свой вклад!
Примечание. Если вы ищете быстрый способ развертывания пользовательских разделителей, вот один из способов использования стирания типов. Мы предполагаем, что вы уже создали класс разделителя, скажем MyDel
так:
struct MyDel { static const pretty_print::delimiters_values<char> values; };
const pretty_print::delimiters_values<char> MyDel::values = { "<", "; ", ">" };
Теперь мы хотим иметь возможность писать std::cout << MyPrinter(v) << std::endl;
для некоторого контейнера, v
используя эти разделители. MyPrinter
будет классом стирания типа, вот так:
struct wrapper_base
{
virtual ~wrapper_base() { }
virtual std::ostream & stream(std::ostream & o) = 0;
};
template <typename T, typename Delims>
struct wrapper : public wrapper_base
{
wrapper(const T & t) : t(t) { }
std::ostream & stream(std::ostream & o)
{
return o << pretty_print::print_container_helper<T, char, std::char_traits<char>, Delims>(t);
}
private:
const T & t;
};
template <typename Delims>
struct MyPrinter
{
template <typename Container> MyPrinter(const Container & c) : base(new wrapper<Container, Delims>(c)) { }
~MyPrinter() { delete base; }
wrapper_base * base;
};
template <typename Delims>
std::ostream & operator<<(std::ostream & o, const MyPrinter<Delims> & p) { return p.base->stream(o); }
источник
pretty_print
пространство имен и предоставление оболочки для использования пользователем при печати. С точки зрения пользователя:std::cout << pretty_print(v);
(возможно, с другим именем). Затем вы можете указать оператора в том же пространстве имен, что и обертка, и затем развернуть его, чтобы печатать что угодно. Вы также можете улучшить оболочку, позволяя при желании определить разделитель для использования в каждом вызове (вместо того, чтобы использовать черты, которые вызывают одинаковый выбор для всего приложения) \Ответы:
Это решение было вдохновлено решением Марсело с несколькими изменениями:
Как и версия Marcelo, она использует черту типа is_container, которая должна быть специализированной для всех поддерживаемых контейнеров. Может быть возможно использовать признак для проверки
value_type
,const_iterator
,begin()
/end()
, но я не уверен , я бы рекомендовал , так как это может соответствовать вещам , которые соответствуют этим критериям , но которые на самом деле не контейнеры, какstd::basic_string
. Также как и версия Marcelo, она использует шаблоны, которые могут быть специализированными для указания используемых разделителей.Основное отличие состоит в том, что я построил свою версию вокруг a
pretty_ostream_iterator
, которая работает аналогично,std::ostream_iterator
но не печатает разделитель после последнего элемента. Форматирование контейнеров выполняется с помощью командыprint_container_helper
, которая может использоваться непосредственно для печати контейнеров без черты is_container или для указания другого типа разделителей.Я также определил is_container и разделители, чтобы он работал для контейнеров с нестандартными предикатами или распределителями, а также для char и wchar_t. Функция operator << также определена для работы с потоками char и wchar_t.
Наконец, я использовал
std::enable_if
, который доступен как часть C ++ 0x и работает в Visual C ++ 2010 и g ++ 4.3 (требуется флаг -std = c ++ 0x) и позже. Таким образом, нет никакой зависимости от Boost.источник
<i, j>
в одной функции, так и[i j]
в другой, вам нужно определить целый новый тип с несколькими статическими членами, чтобы передать этот типprint_container_helper
? Это кажется слишком сложным. Почему бы не перейти к реальному объекту, с полями, которые вы можете установить в каждом конкретном случае, а специализации просто предоставляют разные значения по умолчанию?print_container_helper
не так элегантно, как простоoperator<<
. Конечно, вы всегда можете изменить источник или просто добавить явную специализацию для вашего любимого контейнера, например, дляpair<int, int>
и дляpair<double, string>
. В конечном счете, все зависит от удобства. Предложения по улучшению приветствуются!MyDels
, то я могу сказатьstd::cout << CustomPrinter<MyDels>(x);
. На данный момент я не могу сказатьstd::cout << CustomDelims<"{", ":", "}">(x);
, потому что вы не можете иметьconst char *
аргументы шаблона. Решение сделать константу времени компиляции ограничителей накладывает некоторые ограничения на простоту использования, но я думаю, что оно того стоит.Это было отредактировано несколько раз, и мы решили вызвать основной класс, который обёртывает коллекцию RangePrinter
Это должно работать автоматически с любой коллекцией после того, как вы написали одноразовый оператор << overload, за исключением того, что вам понадобится специальный файл для карт для печати пары, и вы можете захотеть настроить разделитель там.
Вы также можете использовать специальную функцию «print» для использования элемента вместо его непосредственного вывода. Немного похоже на алгоритмы STL, позволяющие передавать пользовательские предикаты. С map вы бы использовали его таким образом, с пользовательским принтером для std :: pair.
Ваш принтер по умолчанию просто выведет его в поток.
Хорошо, давайте работать на нестандартном принтере. Я изменю свой внешний класс на RangePrinter. Итак, у нас есть 2 итератора и несколько разделителей, но мы не настроили, как печатать фактические элементы.
Теперь по умолчанию это будет работать для карт, пока оба типа ключа и значения могут быть напечатаны, и вы можете вставить свой собственный специальный принтер элементов, когда они не (как вы можете с любым другим типом), или если вы не хотите = как разделитель.
Я перемещаю свободную функцию, чтобы создать их до конца сейчас:
Свободная функция (версия итератора) будет выглядеть примерно так, и вы можете даже иметь значения по умолчанию:
Затем вы можете использовать его для std :: set by
Вы также можете написать версию с бесплатной функцией, которая использует пользовательский принтер и версию с двумя итераторами. В любом случае они разрешат параметры шаблона для вас, и вы сможете передавать их как временные.
источник
std::cout << outputFormatter(beginOfRange, endOfRange);
.std::pair
это самый основной пример «внутренней коллекции».std::map
s и работает ли он для коллекций коллекций? Однако я склонен принять это как ответ. Надеюсь Марсело не против, его решение тоже работает.Вот рабочая библиотека, представленная как полная рабочая программа, которую я только что взломал:
В настоящее время он работает только с
vector
иset
, но может быть настроен для работы с большинством контейнеров, просто расширяяIsContainer
специализации. Я не особо задумывался о том, является ли этот код минимальным, но я не могу сразу думать о чем-либо, что я мог бы исключить излишним.РЕДАКТИРОВАТЬ: Просто для удовольствия, я включил версию, которая обрабатывает массивы. Мне пришлось исключить массивы символов, чтобы избежать дальнейших двусмысленностей; с ним все еще могут быть проблемы
wchar_t[]
.источник
std::map<>
либо специализацию оператора, либо определивoperator<<
дляstd::pair<>
.Delims
шаблона класса!operator<<
шаблон соответствует практически чему-либо.Вы можете форматировать контейнеры, а также диапазоны и кортежи, используя библиотеку {fmt} . Например:
печать
к
stdout
.Отказ от ответственности : я автор {fmt}.
источник
Код оказывался удобным в некоторых случаях, и я чувствую, что затраты на настройку очень низки, поскольку использование довольно низкое. Таким образом, я решил выпустить его под лицензией MIT и предоставить репозиторий GitHub, где можно загрузить заголовок и небольшой файл примера.
http://djmuw.github.io/prettycc
0. Предисловие и формулировка
A «украшение» с точкой зрения этого ответа является набором префикса струны, Разделитель-строка, и постфикс струны. Где строка префикса вставляется в поток до, а строка постфикса после значений контейнера (см. 2. Целевые контейнеры). Строка разделителя вставляется между значениями соответствующего контейнера.
Примечание. На самом деле, этот ответ не отвечает на вопрос до 100%, поскольку декорация не является строго скомпилированной постоянной времени, поскольку для проверки того, было ли применено нестандартное декорирование к текущему потоку, требуются проверки времени выполнения. Тем не менее, я думаю, что у этого есть некоторые приличные особенности.
Примечание 2: могут иметь незначительные ошибки, так как он еще не был хорошо протестирован.
1. Общая идея / использование
Нулевой дополнительный код, необходимый для использования
Это должно быть так же легко, как
Простая настройка ...
... по отношению к конкретному объекту потока
или относительно всех потоков:
Грубое описание
ios_base
помощьюxalloc
/pword
, чтобы сохранить указатель наpretty::decor
объект, специально украшающий определенный тип в определенном потоке.Если никакой
pretty::decor<T>
объект для этого потока не был установлен явно,pretty::defaulted<T, charT, chartraitT>::decoration()
вызывается для получения декорации по умолчанию для данного типа. Классpretty::defaulted
должен быть специализированным для настройки декораций по умолчанию.2. Целевые объекты / контейнеры
Целевые объекты
obj
для «красивого украшения» этого кода - это объекты, имеющиеstd::begin
иstd::end
определены (включая массивы в стиле C),begin(obj)
иend(obj)
доступ через ADL,std::tuple
std::pair
.Код включает в себя признак для идентификации классов с особенностями диапазона (
begin
/end
). (Там нет проверки,begin(obj) == end(obj)
является ли выражение допустимым, хотя.)Код предоставляет
operator<<
s в глобальном пространстве имен, которые применяются только к классам, не имеющим более специализированнойoperator<<
доступной версии . Поэтому, напримерstd::string
, не печатается с использованием оператора в этом коде, хотя имеет действительнуюbegin
/end
пару.3. Использование и настройка
Декорации могут быть наложены отдельно для каждого типа (кроме разных
tuple
) и потока (не типа потока!). (То естьstd::vector<int>
может иметь разные декорации для разных потоковых объектов.)А) Стандартное оформление
Префикс по умолчанию
""
(ничего), как и постфикс по умолчанию, а разделитель по умолчанию", "
(запятая + пробел).Б) Настраиваемое оформление по умолчанию для типа путем специализации
pretty::defaulted
шаблона класса.struct defaulted
Имеет статическую функцию - член ,decoration()
возвращающуюdecor
объект , который включает в себя значения по умолчанию для данного типа.Пример использования массива:
Настройка печати по умолчанию:
Распечатать массив массива:
Использование
PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
макроса дляchar
потоковМакрос расширяется до
позволяя переписать вышеуказанную частичную специализацию на
или вставив полную специализацию, как
Другой макрос для
wchar_t
потоков входят:PRETTY_DEFAULT_WDECORATION
.В) накладывать украшения на ручьи
Функция
pretty::decoration
используется для наложения декораций на определенный поток. Существуют перегрузки, принимающие либо один строковый аргумент, являющийся разделителем (принимающий префикс и постфикс из класса по умолчанию), либо три строковых аргумента, собирающих полное оформлениеПолное украшение для данного типа и потока
Настройка разделителя для данного потока
4. Специальная обработка
std::tuple
Вместо того, чтобы разрешать специализацию для каждого возможного типа кортежа, этот код применяет любое украшение, доступное для
std::tuple<void*>
всех видовstd::tuple<...>
s.5. Удалить пользовательские украшения из потока
Чтобы вернуться к декорации по умолчанию для данного типа, используйте
pretty::clear
шаблон функции в потокеs
.5. Дополнительные примеры
Печать "в виде матрицы" с разделителем новой строки
Печать
Посмотреть это на ideone / KKUebZ
6. Код
источник
Я собираюсь добавить еще один ответ здесь, потому что я предложил другой подход к моему предыдущему, то есть использовать языковые аспекты.
Основы здесь
По сути, что вы делаете:
std::locale::facet
. Небольшой недостаток в том, что вам понадобится модуль компиляции где-нибудь для хранения его идентификатора. Давайте назовем это MyPrettyVectorPrinter. Вы, вероятно, дадите ему лучшее имя, а также создадите их для пары и карты.std::has_facet< MyPrettyVectorPrinter >
std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
operator<<
) предоставляет функции по умолчанию. Обратите внимание, что вы можете сделать то же самое для чтения вектора.Мне нравится этот метод, потому что вы можете использовать печать по умолчанию, но при этом можете использовать пользовательское переопределение.
Недостатками являются необходимость библиотеки для вашего аспекта, если она используется в нескольких проектах (поэтому не может быть только заголовками), а также тот факт, что вам необходимо остерегаться затрат на создание нового объекта локали.
Я написал это как новое решение, а не изменяю другое, потому что считаю, что оба подхода могут быть правильными, и вы выбираете.
источник
Цель здесь - использовать 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>
). Может быть, есть более чистый способ сделать это, не уверен.Небольшая библиотека для определения итерации и кортежа:
Библиотека, которая позволяет нам посещать содержимое объекта типа итерация или кортеж:
Красивая библиотека печати:
Тестовый код:
живой пример
При этом используются функции C ++ 14 (некоторые
_t
псевдонимы иauto&&
лямбды), но ни одна из них не обязательна.источник
->
пределахpair
smap
) на этом этапе. Ядро симпатичной библиотеки печати красивое и маленькое, что приятно. Я пытался сделать его легко расширяемым, не уверен, что мне это удастся.Мое решение - просто .h , который является частью пакета scc . Все стандартные контейнеры, карты, наборы, c-массивы можно распечатать.
источник
i
?std::set
пользовательского компаратора или unordered_map с пользовательским равенством. Было бы очень важно поддержать эти конструкции.Выходя из одного из первых BoostCon (теперь он называется CppCon), я и двое других работали над библиотекой, чтобы сделать именно это. Основным камнем преткновения было расширение пространства имен std. Это оказалось бесполезным для библиотеки повышения.
К сожалению, ссылки на код больше не работают, но вы можете найти некоторые интересные моменты в обсуждениях (по крайней мере, те, которые не говорят о том, как его назвать!)
http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html
источник
Вот моя версия реализации, сделанная в 2016 году
Все в одном заголовке, поэтому его легко использовать https://github.com/skident/eos/blob/master/include/eos/io/print.hpp
источник