【问题标题】:{fmt}: How to pass a wrapped iterator to formatter{fmt}:如何将包装的迭代器传递给格式化程序
【发布时间】: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&lt;T&gt;::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&lt;basic_format_context&lt;..., [...]&gt;&gt; 不能转换为basic_format_args&lt;basic_format_context&lt;repr_iterator&lt;...&gt;, [...]&gt;&gt;,因为 fmt 中的所有内容都与格式上下文相关联(godbolt link 与完全错误)。有没有办法通过迭代器包装器来做到这一点,还是我注定要使用 std::string 作为中间格式值?

【问题讨论】:

    标签: c++ iterator fmt


    【解决方案1】:

    我认为您不能像那样交换迭代器类型,并且拥有这样的迭代器适配器不太可能对性能有益。最好先写入中间缓冲区,然后在从缓冲区复制到输出迭代器时进行转换。与适配器方法不同,这可以很容易地针对连续迭代器的常见情况进行优化。

    【讨论】:

      猜你喜欢
      • 2010-11-06
      • 1970-01-01
      • 2015-04-15
      • 1970-01-01
      • 1970-01-01
      • 2011-04-19
      • 1970-01-01
      • 1970-01-01
      • 2011-03-26
      相关资源
      最近更新 更多