Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ {fmt} library: How to leverage builtin format specification parsing for outputting custom type?

Tags:

c++

fmt

This is related to using the {fmt} c++ library.

I have a templated rational type that contains a numerator and denominator that I am formatting by specializing fmt::formatter as described in the documentation. I would like to accept the same format specification that an integral type accepts without having to explicitly write the code to parse the specification.

Based on the documentation, I see that I can derive my specialization of fmt::formatter from fmt::formatter<int> and it will handle parsing of the specification which is great. In the documentation examples, the underlying format method of the formatter is then called to handle the output. In my case, however, I need to format two values separated by a /.

How can I leverage the underlying formatter for formatting both the numerator and denominator?


UPDATE:

The following formatter specialization does the basics and outputs numbers in the format p/q if q is not 1, or just p if q is 1, with the formatting spec applied to p, which is not exactly how I'd like it to work. I would like the specified width in the format string to be applied to the entire output not just the numerator, but I am unsure how to get a handle on that information.

template<rational::Rational T> struct fmt::formatter<T>
    : fmt::formatter<typename T::numerator_type> {
    using base_type = fmt::formatter<typename T::numerator_type>;
    template<class FormatContext>
    auto format(const T& r, FormatContext& ctx) {
        auto n = r.num();
        auto d = r.den();
        if (d == 1)
            return base_type::format(n, ctx);

        base_type::format(n, ctx);
        return format_to(ctx.out(), "/{}", d);
    }
};
like image 266
RandomBits Avatar asked Nov 27 '25 19:11

RandomBits


1 Answers

Since you want the width to be applied to the entire output, you'll need to parse it yourself in the formatter::parse function and then delegate the rest to the element formatter if needed. In the formatter::format function you'll need to format into an intermediate buffer (e.g. fmt::memory_buffer) first and then copy the result into the output padded with fill characters. For example, the chrono formatter does exactly that. The extra copy can be omitted if the width is not specified.

Once formatting of ranges supports width you'll be able to do it in a simpler way by reusing a range formatter with / as a separator and empty delimiters. But right now it is not supported.

like image 52
vitaut Avatar answered Nov 30 '25 09:11

vitaut



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!