漂亮打印 C++ STL 容器
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4850473/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Pretty-print C++ STL containers
提问by Kerrek SB
Please take note of the updates at the end of this post.
请注意本文末尾的更新。
Update: I have created a public project on GitHubfor this library!
更新:我在 GitHub 上为这个库创建了一个公共项目!
I would like to have a single template that once and for all takes care of pretty-printing all STL containers via operator<<
. In pseudo code, I'm looking for something like this:
我想要一个单一的模板,它可以一劳永逸地通过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;
}
Now I've seen plenty of template magic here on SO that I never thought possible, so I'm wondering if anyone can suggest something that would match all containers C. Maybe something trait-ish that can figure out if something has the necessary iterator?
现在我在 SO 上看到了很多模板魔术,我从来没有想过,所以我想知道是否有人可以提出与所有容器 C 匹配的东西。也许可以确定某些东西是否具有必要的迭代器?
Many thanks!
非常感谢!
Update (and solution)
更新(和解决方案)
After raising this problem again on Channel 9, I got a fantastic answer from Sven Groot, which, combined with a bit of SFINAE type traiting, appears to solve the problem in a completely general and nestable fashion. The delimiters may be individually specialised, an example specialization for std::set is included, as well as an example of using custom delimiters.
在第 9 频道再次提出这个问题后,我从 Sven Groot 那里得到了一个绝妙的答案,结合一些 SFINAE 类型特征,似乎以一种完全通用和可嵌套的方式解决了这个问题。分隔符可以单独特化,包括 std::set 特化示例,以及使用自定义分隔符的示例。
The helper "wrap_array()" can be used to print raw C arrays. Update:Pairs and tuples are available for printing; default delimiters are round brackets.
帮助程序“wrap_array()”可用于打印原始 C 数组。更新:对和元组可用于打印;默认分隔符是圆括号。
The enable-if type trait requires C++0x, but with some modifications it should be possible to make a C++98 version of this. Tuples require variadic templates, hence C++0x.
enable-if 类型特征需要 C++0x,但经过一些修改,应该可以制作 C++98 版本。元组需要可变参数模板,因此 C++0x。
I have asked Sven to post the solution here so that I can accept it, but in the meantime I'd like to post the code myself for reference. (Update:Sven has now posted his code below, which I made the accepted answer. My own code uses container type traits, which work for me but may cause unexpected behaviour with non-container classes that provide iterators.)
我已经要求 Sven 在这里发布解决方案,以便我可以接受它,但同时我想自己发布代码以供参考。(更新:Sven 现在在下面发布了他的代码,我做出了可接受的答案。我自己的代码使用容器类型特征,这对我有用,但可能会导致非容器类提供迭代器的意外行为。)
Header (prettyprint.h):
标题(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
Usage example:
用法示例:
#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
;
}
Further ideas for improvements:
进一步的改进想法:
Implement output forUpdate:This is now a separate question on SO! Upupdate:This has now been implemented, thanks to Xeo!std::tuple<...>
in the same way is we have it forstd::pair<S,T>
.Add namespaces so that the helper classes don't bleed into the global namespace.Done- Add template aliases (or something similar) to facilitate making custom delimiter classes, or maybe preprocessor macros?
更新:这现在是关于 SO 的一个单独问题!更新:现在已经实现了,感谢 Xeo!std::tuple<...>
以与for相同的方式实现输出std::pair<S,T>
。添加命名空间,以便帮助程序类不会渗入全局命名空间。完毕- 添加模板别名(或类似的东西)以方便制作自定义分隔符类,或者预处理器宏?
Recent updates:
最近更新:
- I removed the custom output iterator in favour of a simple for loop in the print function.
- All implementation details are now in the
pretty_print
namespace. Only the global stream operators and thepretty_print_array
wrapper are in the global namespace. - Fixed the namespacing so that
operator<<
is now correctly instd
.
- 我删除了自定义输出迭代器,转而使用打印函数中的简单 for 循环。
- 所有实现细节现在都在
pretty_print
命名空间中。只有全局流操作符和pretty_print_array
包装器位于全局命名空间中。 - 修复了命名空间,以便
operator<<
现在正确地在std
.
Notes:
笔记:
- Removing the output iterator means that there is no way to use
std::copy()
to get pretty-printing. I might reinstate the pretty iterator if this is a desired feature, but Sven's code below has the implementation. - It was a conscious design decision to make the delimiters compile-time constants rather than object constants. That means that you cannot supply delimiters dynamically at runtime, but it also means that there's no unneeded overhead. An object-based delimiter configuration has been proposed by Dennis Zickefoose in a comment to Sven's code below. If desired, this could be implemented as an alternative feature.
- It is currently not obvious how to customize nested container delimiters.
- Bear in mind that the purpose of this library is to allow quickcontainer printing facilities that require zero codingon your part. It is not an all-purpose formatting library, but rather a developing tool to alleviate the need to write boiler-plate code for container inspection.
- 移除输出迭代器意味着无法
std::copy()
获得漂亮的打印效果。如果这是一个所需的功能,我可能会恢复漂亮的迭代器,但下面的 Sven 代码具有实现。 - 使分隔符成为编译时常量而不是对象常量是一个有意识的设计决定。这意味着您不能在运行时动态提供定界符,但这也意味着没有不必要的开销。Dennis Zickefoose 在下面对 Sven 代码的评论中提出了基于对象的分隔符配置。如果需要,这可以作为替代功能来实现。
- 目前尚不清楚如何自定义嵌套的容器分隔符。
- 请记住,此库的目的是允许快速容器打印设施,这些设施需要您进行零编码。它不是一个通用的格式化库,而是一个开发工具,可以减轻为容器检查编写样板代码的需要。
Thank you to everyone who contributed!
感谢所有做出贡献的人!
Note:If you are looking for a quick way to deploy custom delimiters, here is one way using type erasure. We assume that you have already constructed a delimiter class, say MyDel
, like so:
注意:如果您正在寻找一种快速部署自定义分隔符的方法,这里是使用类型擦除的一种方法。我们假设您已经构建了一个分隔符类,比如MyDel
,如下所示:
struct MyDel { static const pretty_print::delimiters_values<char> values; };
const pretty_print::delimiters_values<char> MyDel::values = { "<", "; ", ">" };
Now we want to be able to write std::cout << MyPrinter(v) << std::endl;
for some container v
using those delimiters. MyPrinter
will be a type-erasing class, like so:
现在我们希望能够使用这些分隔符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); }
采纳答案by Sven
This solution was inspired by Marcelo's solution, with a few changes:
这个解决方案的灵感来自 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;
}
Like Marcelo's version, it uses an is_container type trait that must be specialized for all containers that are to be supported. It may be possible to use a trait to check for value_type
, const_iterator
, begin()
/end()
, but I'm not sure I'd recommend that since it might match things that match those criteria but aren't actually containers, like std::basic_string
. Also like Marcelo's version, it uses templates that can be specialized to specify the delimiters to use.
与 Marcelo 的版本一样,它使用 is_container 类型特征,该特征必须专用于所有要支持的容器。可能可以使用特征来检查value_type
, const_iterator
, begin()
/ end()
,但我不确定我是否建议这样做,因为它可能匹配符合这些条件但实际上不是容器的事物,例如std::basic_string
. 与 Marcelo 的版本一样,它使用可以专门指定要使用的分隔符的模板。
The major difference is that I've built my version around a pretty_ostream_iterator
, which works similar to the std::ostream_iterator
but doesn't print a delimiter after the last item. Formatting the containers is done by the print_container_helper
, which can be used directly to print containers without an is_container trait, or to specify a different delimiters type.
主要区别在于我围绕 a 构建了我的版本pretty_ostream_iterator
,它的工作原理类似于 ,std::ostream_iterator
但不会在最后一项之后打印分隔符。格式化容器由 完成print_container_helper
,它可以直接用于打印没有 is_container 特征的容器,或指定不同的分隔符类型。
I've also defined is_container and delimiters so it will work for containers with non-standard predicates or allocators, and for both char and wchar_t. The operator<< function itself is also defined to work with both char and wchar_t streams.
我还定义了 is_container 和分隔符,因此它适用于具有非标准谓词或分配器的容器,以及 char 和 wchar_t。operator<< 函数本身也被定义为与 char 和 wchar_t 流一起使用。
Finally, I've used std::enable_if
, which is available as part of C++0x, and works in Visual C++ 2010 and g++ 4.3 (needs the -std=c++0x flag) and later. This way there is no dependency on Boost.
最后,我使用了std::enable_if
,它作为 C++0x 的一部分提供,并且在 Visual C++ 2010 和 g++ 4.3(需要 -std=c++0x 标志)及更高版本中工作。这样就没有对 Boost 的依赖。
回答by CashCow
This has been edited a few times, and we have decided to call the main class that wraps a collection RangePrinter
这已经编辑了几次,我们决定调用包装集合的主类 RangePrinter
This should work automatically with any collection once you have written the one-time operator<< overload, except that you will need a special one for maps to print the pair, and may want to customise the delimiter there.
一旦您编写了一次性 operator<< 重载,这应该会自动与任何集合一起工作,除非您需要一个特殊的映射来打印该对,并且可能希望在那里自定义分隔符。
You could also have a special "print" function to use on the item instead of just outputting it direct. A bit like STL algorithms allow you to pass in custom predicates. With map you would use it this way, with a custom printer for the std::pair.
您还可以在项目上使用特殊的“打印”功能,而不是直接输出它。有点像 STL 算法允许您传入自定义谓词。使用地图,您可以通过这种方式使用它,并为 std::pair 使用自定义打印机。
Your "default" printer would just output it to the stream.
您的“默认”打印机只会将其输出到流中。
Ok, let's work on a custom printer. I will change my outer class to RangePrinter. So we have 2 iterators and some delimiters but have not customised how to print the actual items.
好的,让我们在自定义打印机上工作。我将我的外部类更改为 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;
}
Now by default it will work for maps as long as the key and value types are both printable and you can put in your own special item printer for when they are not (as you can with any other type), or if you do not want = as the delimiter.
现在默认情况下,只要键和值类型都是可打印的,它就适用于地图,并且您可以放入自己的特殊项目打印机,以备不时之需(就像您可以使用任何其他类型一样),或者如果您不想要= 作为分隔符。
I am moving the free-function to create these to the end now:
我正在移动自由功能来创建这些到最后:
A free-function (iterator version) would look like something this and you could even have defaults:
一个自由函数(迭代器版本)看起来像这样,你甚至可以有默认值:
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 );
}
You could then use it for std::set by
然后你可以将它用于 std::set by
std::cout << outputFormatter( mySet );
You can also write free-function version that take a custom printer and ones that take two iterators. In any case they will resolve the template parameters for you, and you will be able to pass them through as temporaries.
您还可以编写带有自定义打印机和带有两个迭代器的免费功能版本。在任何情况下,它们都会为您解析模板参数,您将能够将它们作为临时参数传递。
回答by Marcelo Cantos
Here is a working library, presented as a complete working program, that I just hacked together:
这是一个工作库,以完整的工作程序形式呈现,我刚刚将其组合在一起:
#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";
}
It currently only works with vector
and set
, but can be made to work with most containers, just by expanding on the IsContainer
specializations. I haven't thought much about whether this code is minimal, but I can't immediately think of anything I could strip out as redundant.
它目前仅适用于vector
和set
,但可以通过扩展IsContainer
专业化来与大多数容器一起使用。我没有考虑过这段代码是否是最小的,但我无法立即想到我可以删除的任何多余内容。
EDIT:Just for kicks, I included a version that handles arrays. I had to exclude char arrays to avoid further ambiguities; it might still get into trouble with wchar_t[]
.
编辑:只是为了好玩,我包含了一个处理数组的版本。我不得不排除 char 数组以避免进一步的歧义;它可能仍然会遇到麻烦wchar_t[]
。
回答by vitaut
You can format containers as well as ranges and tuples using the {fmt} library. For example:
您可以使用{fmt} 库格式化容器以及范围和元组。例如:
#include <vector>
#include <fmt/ranges.h>
int main() {
auto v = std::vector<int>{1, 2, 3};
fmt::print("{}", v);
}
prints
印刷
{1, 2, 3}
to stdout
.
到stdout
。
Disclaimer: I'm the author of {fmt}.
免责声明:我是 {fmt} 的作者。
回答by Pixelchemist
The code proved to be handy on several occasions now and I feel the expense to get into customization as usage is quite low. Thus, I decided to release it under MITlicense and provide a GitHub repository where the header and a small example file can be downloaded.
该代码现在多次被证明很方便,我觉得进入定制的成本很低,因为使用率很低。因此,我决定在MIT许可下发布它并提供一个 GitHub 存储库,可以在其中下载标题和一个小示例文件。
http://djmuw.github.io/prettycc
http://djmuw.github.io/prettycc
0. Preface and wording
0. 前言和措辞
A 'decoration'in terms of this answer is a set of prefix-string, delimiter-string, and a postfix-string. Where the prefix string is inserted into a stream before and the postfix string after the values of a container (see 2. Target containers). The delimiter string is inserted between the values of the respective container.
一个“装饰”在这个答案的方面是一组前缀字符串,分隔字符串,以及后缀串的。其中前缀字符串插入到容器的值之前和后缀字符串之后的流中(请参阅 2. 目标容器)。分隔符字符串插入到相应容器的值之间。
Note: Actually, this answer does not address the question to 100% since the decoration is not strictly compiled time constant because runtime checks are required to check whether a custom decoration has been applied to the current stream.Nevertheless, I think it has some decent features.
注意:实际上,这个答案并没有解决 100% 的问题,因为装饰不是严格的编译时间常数,因为需要运行时检查来检查自定义装饰是否已应用于当前流。尽管如此,我认为它有一些不错的功能。
Note2: May have minor bugs since it is not yet well tested.
注2:可能有小错误,因为它还没有经过很好的测试。
1. General idea/usage
1. 总体思路/用法
Zero additional code required for usage
使用所需的零额外代码
It is to be kept as easy as
它应该保持简单
#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;
}
Easy customization ...
轻松定制...
... with respect to a specific stream object
...关于特定的流对象
#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;
}
or with respect to all streams:
或关于所有流:
#include <vector>
#include "pretty.h"
// set decoration for std::vector<int> for all ostream objects
PRETTY_DEFAULT_DECORATION(std::vector<int>, "{", ", ", "}")
int main()
{
std::cout << std::vector<int>{1,2,3,4,5}; // prints {1, 2, 3, 4, 5}
std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
return 0;
}
Rough description
粗略描述
- The code includes a class template providing a default decoration for any type
- which can be specialized to change the default decoration for (a) certain type(s) and it is
- using the private storage provided by
ios_base
usingxalloc
/pword
in order to save a pointer to apretty::decor
object specifically decorating a certain type on a certain stream.
- 代码包含一个类模板,为任何类型提供默认装饰
- 它可以专门用于更改(a)某些类型的默认装饰,它是
- 使用通过
ios_base
使用xalloc
/提供的私有存储pword
来保存指向pretty::decor
特定流上特定类型的对象的指针。
If no pretty::decor<T>
object for this stream has been set up explicitly pretty::defaulted<T, charT, chartraitT>::decoration()
is called to obtain the default decoration for the given type.
The class pretty::defaulted
is to be specialized to customize default decorations.
如果没有pretty::decor<T>
显式设置此流的对象,pretty::defaulted<T, charT, chartraitT>::decoration()
则调用以获取给定类型的默认装饰。该类pretty::defaulted
将专门用于自定义默认装饰。
2. Target objects / containers
2. 目标对象/容器
Target objects obj
for the 'pretty decoration'of this code are objects having either
此代码obj
的“漂亮装饰”的目标对象是具有以下任一特征的对象
- overloads
std::begin
andstd::end
defined (includes C-Style arrays), - having
begin(obj)
andend(obj)
available via ADL, - are of type
std::tuple
- or of type
std::pair
.
- 重载
std::begin
和std::end
定义(包括 C 样式数组), - 拥有
begin(obj)
并end(obj)
通过 ADL 可用, - 属于类型
std::tuple
- 或类型
std::pair
.
The code includes a trait for identification of classes with range features (begin
/end
).
(There's no check included, whether begin(obj) == end(obj)
is a valid expression, though.)
该代码包括用于识别具有范围特征 ( begin
/ end
)的类的特征。(不过,不包括检查,是否begin(obj) == end(obj)
是有效的表达式。)
The code provides operator<<
s in the global namespace that only apply to classes not having a more specialized version of operator<<
available.
Therefore, for example std::string
is not printed using the operator in this code although having a valid begin
/end
pair.
代码operator<<
在全局命名空间中提供了s,这些 s 仅适用于没有更专门版本的operator<<
可用版本。因此,例如std::string
,尽管具有有效的begin
/end
对,但不会在此代码中使用运算符进行打印。
3. Utilization and customization
3.利用和定制
Decorations can be imposed separately for every type (except different tuple
s) and stream (not stream type!).
(I.e. a std::vector<int>
can have different decorations for different stream objects.)
可以为每种类型(除了不同的tuple
s)和流(不是流类型!)单独施加装饰。(即,std::vector<int>
对于不同的流对象,可以有不同的装饰。)
A) Default decoration
A) 默认装饰
The default prefix is ""
(nothing) as is the default postfix, while the default delimiter is ", "
(comma+space).
默认前缀是""
(无)作为默认后缀,而默认分隔符是", "
(逗号+空格)。
B) Customized default decoration of a type by specializing the pretty::defaulted
class template
B) 通过专门化pretty::defaulted
类模板来自定义类型的默认装饰
The struct defaulted
has a static member function decoration()
returning a decor
object which includes the default values for the given type.
在struct defaulted
具有静态成员函数decoration()
返回一个decor
对象,其包括用于给定类型的默认值。
Example using an array:
使用数组的示例:
Customize default array printing:
自定义默认阵列打印:
namespace pretty
{
template<class T, std::size_t N>
struct defaulted<T[N]>
{
static decor<T[N]> decoration()
{
return{ { "(" }, { ":" }, { ")" } };
}
};
}
Print an arry array:
打印一个 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)
Using the PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
macro for char
streams
将PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
宏用于char
流
The macro expands to
宏扩展为
namespace pretty {
template< __VA_ARGS__ >
struct defaulted< TYPE > {
static decor< TYPE > decoration() {
return { PREFIX, DELIM, POSTFIX };
}
};
}
enabling the above partial specialization to be rewritten to
使上述部分专业化能够被重写为
PRETTY_DEFAULT_DECORATION(T[N], "", ";", "", class T, std::size_t N)
or inserting a full specialization like
或插入一个完整的专业化,如
PRETTY_DEFAULT_DECORATION(std::vector<int>, "(", ", ", ")")
Another macro for wchar_t
streams is included: PRETTY_DEFAULT_WDECORATION
.
包括另一个wchar_t
流的宏:PRETTY_DEFAULT_WDECORATION
.
C) Impose decoration on streams
C) 对流进行装饰
The function pretty::decoration
is used to impose a decoration on a certain stream.
There are overloads taking either
- one string argument being the delimiter (adopting prefix and postfix from the defaulted class)
- or three string arguments assembling the complete decoration
该函数pretty::decoration
用于对某个流进行装饰。有重载 - 一个字符串参数作为分隔符(采用来自默认类的前缀和后缀) - 或三个字符串参数组合完整的装饰
Complete decoration for given type and stream
给定类型和流的完整装饰
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
Customization of delimiter for given stream
给定流的定界符自定义
PRETTY_DEFAULT_DECORATION(float[3], "{{{", ",", "}}}")
std::stringstream v;
v << e; // prints {{{3.4,4.3,5.2}}}
v << pretty::decoration<float[3]>(":");
v << e; // prints {{{3.4:4.3:5.2}}}
v << pretty::decoration<float[3]>("((", "=", "))");
v << e; // prints ((3.4=4.3=5.2))
4. Special handling of std::tuple
4. 特殊处理 std::tuple
Instead of allowing a specialization for every possible tuple type, this code applies any decoration available for std::tuple<void*>
to all kind of std::tuple<...>
s.
这段代码没有允许对每个可能的元组类型进行专门化,而是将任何可用的修饰应用于std::tuple<void*>
所有类型的std::tuple<...>
s。
5. Remove custom decoration from the stream
5.从流中删除自定义装饰
To go back to the defaulted decoration for a given type use pretty::clear
function template on the stream s
.
要返回给定类型的默认装饰,请pretty::clear
在流上使用函数模板s
。
s << pretty::clear<std::vector<int>>();
5. Further examples
5. 进一步的例子
Printing "matrix-like" with newline delimiter
用换行符打印“矩阵式”
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;
Prints
印刷
1, 2, 3
4, 5, 6
7, 8, 9
See it on ideone/KKUebZ
在ideone/KKUebZ 上查看
6. Code
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_
回答by CashCow
I am going to add another answer here, because I have come up with a different approach to my previous one, and that is to use locale facets.
我将在这里添加另一个答案,因为我想出了一种与上一个不同的方法,那就是使用语言环境方面。
The basics are here
基础知识在这里
Essentially what you do is:
基本上你要做的是:
- Create a class that derives from
std::locale::facet
. The slight downside is that you will need a compilation unit somewhere to hold its id. Let's call it MyPrettyVectorPrinter. You'd probably give it a better name, and also create ones for pair and map. - In your stream function, you check
std::has_facet< MyPrettyVectorPrinter >
- If that returns true, extract it with
std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
- Your facet objects will have values for the delimiters and you can read them. If the facet isn't found, your print function (
operator<<
) provides default ones. Note you can do the same thing for reading a vector.
- 创建一个派生自
std::locale::facet
. 一个小小的缺点是你需要一个编译单元在某个地方来保存它的 id。我们称之为 MyPrettyVectorPrinter。你可能会给它一个更好的名字,并为pair和map创建一个。 - 在您的流函数中,您检查
std::has_facet< MyPrettyVectorPrinter >
- 如果返回 true,则使用
std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
- 您的构面对象将具有分隔符的值,您可以读取它们。如果未找到构面,您的打印函数 (
operator<<
) 将提供默认构面。请注意,您可以对读取向量执行相同的操作。
I like this method because you can use a default print whilst still being able to use a custom override.
我喜欢这种方法,因为您可以使用默认打印,同时仍然可以使用自定义覆盖。
The downsides are needing a library for your facet if used in multiple projects (so can't just be headers-only) and also the fact that you need to beware about the expense of creating a new locale object.
缺点是如果在多个项目中使用你的方面需要一个库(所以不能只是头文件),而且你需要注意创建新语言环境对象的费用。
I have written this as a new solution rather than modify my other one because I believe both approaches can be correct and you take your pick.
我已将此作为新解决方案编写,而不是修改我的另一个解决方案,因为我相信这两种方法都是正确的,您可以选择。
回答by Yakk - Adam Nevraumont
The goal here is to use ADL to do customization of how we pretty print.
这里的目标是使用 ADL 来定制我们如何漂亮地打印。
You pass in a formatter tag, and override 4 functions (before, after, between and descend) in the tag's namespace. This changes how the formatter prints 'adornments' when iterating over containers.
您传入一个格式化程序标记,并覆盖标记命名空间中的 4 个函数(之前、之后、之间和下降)。这会改变格式化程序在迭代容器时打印“装饰”的方式。
A default formatter that does {(a->b),(c->d)}
for maps, (a,b,c)
for tupleoids, "hello"
for strings, [x,y,z]
for everything else included.
一个默认的格式化程序,{(a->b),(c->d)}
用于映射、(a,b,c)
元组、"hello"
字符串[x,y,z]
以及其他所有内容。
It should "just work" with 3rd party iterable types (and treat them like "everything else").
它应该与 3rd 方可迭代类型“正常工作”(并将它们视为“其他一切”)。
If you want custom adornments for your 3rd party iterables, simply create your own tag. It will take a bit of work to handle map descent (you need to overload pretty_print_descend( your_tag
to return pretty_print::decorator::map_magic_tag<your_tag>
). Maybe there is a cleaner way to do this, not sure.
如果您想要为您的 3rd 方迭代器定制装饰,只需创建您自己的标签。处理地图下降需要一些工作(您需要重载pretty_print_descend( your_tag
返回pretty_print::decorator::map_magic_tag<your_tag>
)。也许有一种更清洁的方法可以做到这一点,不确定。
A little library to detect iterability, and tuple-ness:
一个检测可迭代性和元组性的小库:
namespace details {
using std::begin; using std::end;
template<class T, class=void>
struct is_iterable_test:std::false_type{};
template<class T>
struct is_iterable_test<T,
decltype((void)(
(void)(begin(std::declval<T>())==end(std::declval<T>()))
, ((void)(std::next(begin(std::declval<T>()))))
, ((void)(*begin(std::declval<T>())))
, 1
))
>:std::true_type{};
template<class T>struct is_tupleoid:std::false_type{};
template<class...Ts>struct is_tupleoid<std::tuple<Ts...>>:std::true_type{};
template<class...Ts>struct is_tupleoid<std::pair<Ts...>>:std::true_type{};
// template<class T, size_t N>struct is_tupleoid<std::array<T,N>>:std::true_type{}; // complete, but problematic
}
template<class T>struct is_iterable:details::is_iterable_test<std::decay_t<T>>{};
template<class T, std::size_t N>struct is_iterable<T(&)[N]>:std::true_type{}; // bypass decay
template<class T>struct is_tupleoid:details::is_tupleoid<std::decay_t<T>>{};
template<class T>struct is_visitable:std::integral_constant<bool, is_iterable<T>{}||is_tupleoid<T>{}> {};
A library that lets us visit the contents of an iterable or tuple type object:
一个让我们访问可迭代或元组类型对象内容的库:
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) );
}
A pretty printing library:
一个漂亮的打印库:
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) );
}
}
Test code:
测试代码:
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";
}
This does use C++14 features (some _t
aliases, and auto&&
lambdas), but none are essential.
这确实使用了 C++14 特性(一些_t
别名和auto&&
lambdas),但没有一个是必不可少的。
回答by Leonid Volnitsky
回答by Jeffrey Faust
Coming out of one of the first BoostCon (now called CppCon), I and two others worked on a library to do just this. The main sticking point was needing to extend namespace std. That turned out to be a no-go for a boost library.
从第一个 BoostCon(现在称为 CppCon)中出来,我和另外两个人在一个库上工作来做到这一点。主要的症结在于需要扩展命名空间 std。事实证明,这对 boost 库来说是不可行的。
Unfortunately the links to the code no longer work, but you might find some interesting tidbits in the discussions (at least those that aren't talking about what to name it!)
不幸的是,代码的链接不再有效,但您可能会在讨论中发现一些有趣的花絮(至少那些没有讨论如何命名的!)
http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html
http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html
回答by Skident
Here is my version of implementation done in 2016
这是我在 2016 年完成的实施版本
Everything in one header, so it's easy to use https://github.com/skident/eos/blob/master/include/eos/io/print.hpp
所有内容都在一个标题中,因此易于使用 https://github.com/skident/eos/blob/master/include/eos/io/print.hpp
/*! \file print.hpp
* \brief Useful functions for work with STL containers.
*
* Now it supports generic print for STL containers like: [elem1, elem2, elem3]
* Supported STL conrainers: vector, deque, list, set multiset, unordered_set,
* map, multimap, unordered_map, array
*
* \author Skident
* \date 02.09.2016
* \copyright Skident Inc.
*/
#pragma once
// check is the C++11 or greater available (special hack for MSVC)
#if (defined(_MSC_VER) && __cplusplus >= 199711L) || __cplusplus >= 201103L
#define MODERN_CPP_AVAILABLE 1
#endif
#include <iostream>
#include <sstream>
#include <vector>
#include <deque>
#include <set>
#include <list>
#include <map>
#include <cctype>
#ifdef MODERN_CPP_AVAILABLE
#include <array>
#include <unordered_set>
#include <unordered_map>
#include <forward_list>
#endif
#define dump(value) std::cout << (#value) << ": " << (value) << std::endl
#define BUILD_CONTENT \
std::stringstream ss; \
for (; it != collection.end(); ++it) \
{ \
ss << *it << elem_separator; \
} \
#define BUILD_MAP_CONTENT \
std::stringstream ss; \
for (; it != collection.end(); ++it) \
{ \
ss << it->first \
<< keyval_separator \
<< it->second \
<< elem_separator; \
} \
#define COMPILE_CONTENT \
std::string data = ss.str(); \
if (!data.empty() && !elem_separator.empty()) \
data = data.substr(0, data.rfind(elem_separator)); \
std::string result = first_bracket + data + last_bracket; \
os << result; \
if (needEndl) \
os << std::endl; \
////
///
///
/// Template definitions
///
///
//generic template for classes: deque, list, forward_list, vector
#define VECTOR_AND_CO_TEMPLATE \
template< \
template<class T, \
class Alloc = std::allocator<T> > \
class Container, class Type, class Alloc> \
#define SET_TEMPLATE \
template< \
template<class T, \
class Compare = std::less<T>, \
class Alloc = std::allocator<T> > \
class Container, class T, class Compare, class Alloc> \
#define USET_TEMPLATE \
template< \
template < class Key, \
class Hash = std::hash<Key>, \
class Pred = std::equal_to<Key>, \
class Alloc = std::allocator<Key> \
> \
class Container, class Key, class Hash, class Pred, class Alloc \
> \
#define MAP_TEMPLATE \
template< \
template<class Key, \
class T, \
class Compare = std::less<Key>, \
class Alloc = std::allocator<std::pair<const Key,T> > \
> \
class Container, class Key, \
class Value/*, class Compare, class Alloc*/> \
#define UMAP_TEMPLATE \
template< \
template<class Key, \
class T, \
class Hash = std::hash<Key>, \
class Pred = std::equal_to<Key>, \
class Alloc = std::allocator<std::pair<const Key,T> >\
> \
class Container, class Key, class Value, \
class Hash, class Pred, class Alloc \
> \
#define ARRAY_TEMPLATE \
template< \
template<class T, std::size_t N> \
class Array, class Type, std::size_t Size> \
namespace eos
{
static const std::string default_elem_separator = ", ";
static const std::string default_keyval_separator = " => ";
static const std::string default_first_bracket = "[";
static const std::string default_last_bracket = "]";
//! Prints template Container<T> as in Python
//! Supported containers: vector, deque, list, set, unordered_set(C++11), forward_list(C++11)
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
template<class Container>
void print( const Container& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}
//! Prints collections with one template argument and allocator as in Python.
//! Supported standard collections: vector, deque, list, forward_list
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
VECTOR_AND_CO_TEMPLATE
void print( const Container<Type>& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<Type>::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}
//! Prints collections like std:set<T, Compare, Alloc> as in Python
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
SET_TEMPLATE
void print( const Container<T, Compare, Alloc>& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<T, Compare, Alloc>::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}
//! Prints collections like std:unordered_set<Key, Hash, Pred, Alloc> as in Python
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
USET_TEMPLATE
void print( const Container<Key, Hash, Pred, Alloc>& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<Key, Hash, Pred, Alloc>::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}
//! Prints collections like std:map<T, U> as in Python
//! supports generic objects of std: map, multimap
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
MAP_TEMPLATE
void print( const Container<Key, Value>& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& keyval_separator = default_keyval_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<Key, Value>::const_iterator it = collection.begin();
BUILD_MAP_CONTENT
COMPILE_CONTENT
}
//! Prints classes like std:unordered_map as in Python
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
UMAP_TEMPLATE
void print( const Container<Key, Value, Hash, Pred, Alloc>& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& keyval_separator = default_keyval_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Container<Key, Value, Hash, Pred, Alloc>::const_iterator it = collection.begin();
BUILD_MAP_CONTENT
COMPILE_CONTENT
}
//! Prints collections like std:array<T, Size> as in Python
//! \param collection which should be printed
//! \param elem_separator the separator which will be inserted between elements of collection
//! \param keyval_separator separator between key and value of map. For default it is the '=>'
//! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
//! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
ARRAY_TEMPLATE
void print( const Array<Type, Size>& collection
, const std::string& elem_separator = default_elem_separator
, const std::string& first_bracket = default_first_bracket
, const std::string& last_bracket = default_last_bracket
, std::ostream& os = std::cout
, bool needEndl = true
)
{
typename Array<Type, Size>::const_iterator it = collection.begin();
BUILD_CONTENT
COMPILE_CONTENT
}
//! Removes all whitespaces before data in string.
//! \param str string with data
//! \return string without whitespaces in left part
std::string ltrim(const std::string& str);
//! Removes all whitespaces after data in string
//! \param str string with data
//! \return string without whitespaces in right part
std::string rtrim(const std::string& str);
//! Removes all whitespaces before and after data in string
//! \param str string with data
//! \return string without whitespaces before and after data in string
std::string trim(const std::string& str);
////////////////////////////////////////////////////////////
////////////////////////ostream logic//////////////////////
/// Should be specified for concrete containers
/// because of another types can be suitable
/// for templates, for example templates break
/// the code like this "cout << string("hello") << endl;"
////////////////////////////////////////////////////////////
#define PROCESS_VALUE_COLLECTION(os, collection) \
print( collection, \
default_elem_separator, \
default_first_bracket, \
default_last_bracket, \
os, \
false \
); \
#define PROCESS_KEY_VALUE_COLLECTION(os, collection) \
print( collection, \
default_elem_separator, \
default_keyval_separator, \
default_first_bracket, \
default_last_bracket, \
os, \
false \
); \
///< specialization for vector
template<class T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for deque
template<class T>
std::ostream& operator<<(std::ostream& os, const std::deque<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for list
template<class T>
std::ostream& operator<<(std::ostream& os, const std::list<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for set
template<class T>
std::ostream& operator<<(std::ostream& os, const std::set<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for multiset
template<class T>
std::ostream& operator<<(std::ostream& os, const std::multiset<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
#ifdef MODERN_CPP_AVAILABLE
///< specialization for unordered_map
template<class T>
std::ostream& operator<<(std::ostream& os, const std::unordered_set<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for forward_list
template<class T>
std::ostream& operator<<(std::ostream& os, const std::forward_list<T>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for array
template<class T, std::size_t N>
std::ostream& operator<<(std::ostream& os, const std::array<T, N>& collection)
{
PROCESS_VALUE_COLLECTION(os, collection)
return os;
}
#endif
///< specialization for map, multimap
MAP_TEMPLATE
std::ostream& operator<<(std::ostream& os, const Container<Key, Value>& collection)
{
PROCESS_KEY_VALUE_COLLECTION(os, collection)
return os;
}
///< specialization for unordered_map
UMAP_TEMPLATE
std::ostream& operator<<(std::ostream& os, const Container<Key, Value, Hash, Pred, Alloc>& collection)
{
PROCESS_KEY_VALUE_COLLECTION(os, collection)
return os;
}
}