【发布时间】:2021-12-11 07:24:46
【问题描述】:
我正在使用 C++ {fmt} 库,特别是制作一个简单的 repr 包装器,将对象表示转换为可打印的表示('\n'、'\x02' 等字符转换为 "\ n", "\x02", ...)。虽然已经有一个 library 具有与此类似的功能,但我不喜欢创建临时字符串缓冲区只是为了在其中存储格式化的替换字段、转换其中的字符并在之后销毁它的想法:
#include <string_view>
#include <iterator>
#include <fmt/core.h>
#include <fmt/format.h>
template <typename T>
concept Formattable = fmt::has_formatter<T, fmt::format_context>::value;
template <typename T> requires Formattable<T>
struct Repr
{
const T& t;
explicit Repr(const T& t) : t(t)
{}
};
template <typename OutputIt>
struct repr_adapter
{
OutputIt& it;
explicit repr_adapter(OutputIt& it) : it(it)
{}
repr_adapter& operator=(char c)
{
switch (c)
{
case '\a':
*it++ = '\\';
*it++ = 'a';
break;
case '\b':
*it++ = '\\';
*it++ = 'b';
break;
case '\x1b':
*it++ = '\\';
*it++ = 'e';
break;
case '\f':
*it++ = '\\';
*it++ = 'f';
break;
case '\n':
*it++ = '\\';
*it++ = 'n';
break;
case '\r':
*it++ = '\\';
*it++ = 'r';
break;
case '\t':
*it++ = '\\';
*it++ = 't';
break;
case '\v':
*it++ = '\\';
*it++ = 'v';
break;
case '\\':
*it++ = '\\';
*it++ = '\\';
break;
case '\'':
*it++ = '\\';
*it++ = '\'';
break;
case '\"':
*it++ = '\\';
*it++ = '\"';
break;
default:
if (' ' <= c && c <= '~')
*it++ = c;
else
it = fmt::format_to(it, "\\x{:02x}", c);
}
return *this;
}
};
template <typename OutputIt>
struct repr_iterator : std::iterator_traits<OutputIt>
{
// Is a pointer so that it's copyable
OutputIt* iterator;
repr_iterator(OutputIt& iterator) : iterator(&iterator)
{}
repr_adapter<OutputIt> operator*()
{
return repr_adapter<OutputIt>{*iterator};
}
repr_iterator& operator++()
{
return *this;
}
repr_iterator operator++(int)
{
return *this;
}
};
// Actually important code starts here
template <typename T>
struct fmt::formatter<Repr<T>> : public fmt::formatter<T>
{
using fmt::formatter<T>::parse;
template<typename FormatContext>
auto format(Repr<T> repr, FormatContext& ctx)
{
// Working version (but does not actually modify format output)
return fmt::formatter<T>::format(repr.t, ctx);
}
};
int main()
{
fmt::print("{}\n", Repr<const char*>{"abc\ndef"});
}
通过制作一个脆弱的输出迭代器包装器并使用默认格式,我几乎成功了:
template<typename FormatContext>
auto format(Repr<T> repr, FormatContext& ctx)
{
// Working version (converts \n and stuff but uses the default format,
// ignores the specifier parse result)
auto it = ctx.out();
repr_iterator<decltype(it)> repr_it{ it };
fmt::format_to(repr_it, "{}", repr.t);
return *repr_it.iterator;
}
,但问题是要调用包装的formatter<T>::format(使用已解析的说明符)我需要以某种方式创建fmt::basic_format_context 的实例,其中我的包装repr_iterator 来自FormatContext,我正在传入包装格式化程序:
auto format(Repr<T> repr, FormatContext& ctx)
{
// Does not work
auto it = ctx.out();
repr_iterator<decltype(it)> repr_it{ &it };
return fmt::formatter<T>::format(
repr.t,
fmt::basic_format_context<decltype(repr_it), char>{
repr_it,
ctx.args(),
ctx.locale()
}
);
}
这不起作用并产生一个错误,如果我理解正确,正确地抱怨basic_format_args<basic_format_context<..., [...]>> 不能转换为basic_format_args<basic_format_context<repr_iterator<...>, [...]>>,因为 fmt 中的所有内容都与格式上下文相关联(godbolt link 与完全错误)。有没有办法通过迭代器包装器来做到这一点,还是我注定要使用 std::string 作为中间格式值?
【问题讨论】: