该代码现在多次被证明很方便,我觉得进行定制的费用很低,因为使用率很低。因此,我决定在MIT许可下发布它,并提供一个 GitHub 存储库,可以在其中下载标头和一个小示例文件。
0. 前言和措辞
就这个答案而言, “装饰”是一组前缀字符串、分隔符字符串和后缀字符串。前缀字符串插入到流之前,后缀字符串插入到容器的值之后(请参阅 2. 目标容器)。分隔符字符串插入到相应容器的值之间。
注意:实际上,这个答案并没有解决 100% 的问题,因为装饰不是严格编译的时间常数,因为需要运行时检查来检查自定义装饰是否已应用于当前流。
不过,我认为它有一些不错的功能。
注意2:可能有一些小错误,因为它还没有经过很好的测试。
1. 总体思路/用法
使用所需的零附加代码
保持简单
#include <vector>
#include "pretty.h"
int main()
{
std::cout << std::vector<int>{1,2,3,4,5}; // prints 1, 2, 3, 4, 5
return 0;
}
轻松定制...
...关于特定的流对象
#include <vector>
#include "pretty.h"
int main()
{
// set decoration for std::vector<int> for cout object
std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
return 0;
}
或关于所有流:
#include <vector>
#include "pretty.h"
// set decoration for std::vector<int> for all ostream objects
PRETTY_DEFAULT_DECORATION(std::vector<int>, "{", ", ", "}")
int main()
{
std::cout << std::vector<int>{1,2,3,4,5}; // prints {1, 2, 3, 4, 5}
std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
return 0;
}
粗略的描述
- 代码包含一个类模板,为任何类型提供默认装饰
- 它可以专门用于更改(a)某些类型的默认装饰,它是
ios_base
使用xalloc
/提供的私有存储,pword
以保存指向pretty::decor
特定流上特定类型的对象的指针。
如果没有pretty::decor<T>
明确设置此流的对象,pretty::defaulted<T, charT, chartraitT>::decoration()
则调用以获取给定类型的默认装饰。该类pretty::defaulted
将专门用于自定义默认装饰。
2.目标对象/容器
此代码obj
的“漂亮装饰”的目标对象是具有以下任一属性的对象
- 重载
std::begin
和std::end
定义(包括 C 样式数组),
- 拥有
begin(obj)
并end(obj)
通过 ADL 获得,
- 是类型
std::tuple
- 或类型
std::pair
。
该代码包含一个用于识别具有范围特征 ( begin
/ end
) 的类的特征。(不过,没有检查是否begin(obj) == end(obj)
是一个有效的表达式。)
该代码operator<<
在全局命名空间中提供了 s,这些名称仅适用于没有更专业版本的operator<<
可用的类。因此,例如,std::string
尽管有一个有效的begin
/end
对,但在此代码中不使用运算符打印。
3.利用和定制
tuple
可以为每种类型(不同的 s 除外)和流(不是流类型!)单独施加装饰。(即astd::vector<int>
可以对不同的流对象有不同的装饰。)
A) 默认装饰
默认前缀是""
(nothing) 和默认后缀一样,而默认分隔符是", "
(comma+space)。
pretty::defaulted
B)通过专门化类模板自定义类型的默认装饰
有struct defaulted
一个静态成员函数,decoration()
返回一个decor
包含给定类型的默认值的对象。
使用数组的示例:
自定义默认数组打印:
namespace pretty
{
template<class T, std::size_t N>
struct defaulted<T[N]>
{
static decor<T[N]> decoration()
{
return{ { "(" }, { ":" }, { ")" } };
}
};
}
打印一个数组:
float e[5] = { 3.4f, 4.3f, 5.2f, 1.1f, 22.2f };
std::cout << e << '\n'; // prints (3.4:4.3:5.2:1.1:22.2)
将PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
宏用于char
流
宏扩展为
namespace pretty {
template< __VA_ARGS__ >
struct defaulted< TYPE > {
static decor< TYPE > decoration() {
return { PREFIX, DELIM, POSTFIX };
}
};
}
使上述部分专业化重写为
PRETTY_DEFAULT_DECORATION(T[N], "", ";", "", class T, std::size_t N)
或插入一个完整的专业,如
PRETTY_DEFAULT_DECORATION(std::vector<int>, "(", ", ", ")")
流的另一个宏wchar_t
包括:PRETTY_DEFAULT_WDECORATION
.
C) 对溪流进行装饰
该函数pretty::decoration
用于对某个流进行装饰。有重载采用 - 一个字符串参数作为分隔符(采用默认类的前缀和后缀) - 或三个字符串参数组装完整的装饰
给定类型和流的完整装饰
float e[3] = { 3.4f, 4.3f, 5.2f };
std::stringstream u;
// add { ; } decoration to u
u << pretty::decoration<float[3]>("{", "; ", "}");
// use { ; } decoration
u << e << '\n'; // prints {3.4; 4.3; 5.2}
// uses decoration returned by defaulted<float[3]>::decoration()
std::cout << e; // prints 3.4, 4.3, 5.2
为给定流自定义分隔符
PRETTY_DEFAULT_DECORATION(float[3], "{{{", ",", "}}}")
std::stringstream v;
v << e; // prints {{{3.4,4.3,5.2}}}
v << pretty::decoration<float[3]>(":");
v << e; // prints {{{3.4:4.3:5.2}}}
v << pretty::decoration<float[3]>("((", "=", "))");
v << e; // prints ((3.4=4.3=5.2))
4.特殊处理std::tuple
这段代码没有允许对每种可能的元组类型进行专门化,而是将任何可用于std::tuple<void*>
所有类型的修饰应用到std::tuple<...>
s。
5.从流中删除自定义装饰
要返回给定类型的默认装饰,请使用pretty::clear
流上的函数模板s
。
s << pretty::clear<std::vector<int>>();
5. 进一步的例子
使用换行符打印“类似矩阵”
std::vector<std::vector<int>> m{ {1,2,3}, {4,5,6}, {7,8,9} };
std::cout << pretty::decoration<std::vector<std::vector<int>>>("\n");
std::cout << m;
印刷
1, 2, 3
4, 5, 6
7, 8, 9
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_