I've looked at several articles on custom formatters for the C++20 formatting library, but many seem subtly incorrect or fail to compile on my toolchain. Below is an example I believe is correct followed by 3 specific questions.
#include <print>
struct CustomType {
int a;
};
template<>
struct std::formatter<CustomType> : std::formatter<int> {
// use format specifiers for int
using std::formatter<int>::parse;
auto format(CustomType const& x, auto & ctx) const {
auto out = ctx.out();
out = std::format_to(out, "[");
ctx.advance_to(out);
out = std::formatter<int>::format(x.a, ctx);
return std::format_to(out, "]");
}
};
int main() {
std::println("{:>2}", CustomType{1});
return 0;
}
Are these statements correct?
format
method should be templated on the format context type instead of using std::format_context
, as many online examples suggest.format
method should be const qualified.format_to()
. Most examples omit this and seem to assume a std::back_insert_iterator
, but as far as I can tell the standard does not mandate that even for std::format_context
.The format method should be templated on the format context type instead of using std::format_context, as many online examples suggest.
Yes.
The format method should be const qualified.
Yes.
The output iterator needs to be manually updated when using format_to(). Most examples omit this and seem to assume a std::back_insert_iterator, but as far as I can tell the standard does not mandate that even for std::format_context.
Yes. While the library tends to use the same, one iterator type for all char types, this isn't the only thing you can do - and if format
is ever instantiated with a context that has an iterator type for which position is important - like, say, char*
- then if you don't advance_to
, your next format will overwrite your first one (e.g. the FMT_COMPILE
approach in {fmt}
doesn't wrap the iterator, so you can create a situation where the output iterator is char*
and then this happens).
For this reason it might be less error prone to:
ctx.advance_to(std::format_to(ctx.out(), "["));
ctx.advance_to(std::formatter<int>::format(x.a, ctx));
return std::format_to(ctx.out(), "]");
Although that's admittedly more tedious. It'd be nice if there was better library support for this sort of thing, but I'm not sure what that would look like.