Specializing std::formatter
The std::formatter class template can be specialized to allow your custom types to work with std::format, std::print (C++23), and related formatting functions. This is the modern alternative to overloading stream operators for output.
The Basic Pattern
Here's a point type with a formatter specialization:
#include <format>
struct point {
int x;
int y;
};
template<>
struct std::formatter<point> {
constexpr auto parse(std::format_parse_context& ctx) {
return ctx.begin();
}
auto format(const point& p, std::format_context& ctx) const {
return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};Now point works with any formatting function:
point p{3, 4};
std::string s = std::format("Location: {}", p); // "Location: (3, 4)"
std::print("Point: {}\n", p); // C++23: prints "Point: (3, 4)"Every specialization needs these two methods. The parse method receives a context where ctx.begin() points to the first character after the colon (or directly to } if there's no colon) and must return an iterator to the closing }. The format method writes to ctx.out() and returns the updated output iterator.
Custom Format Specifiers
The basic pattern ignores format specifiers. To support them, parse the specifier in parse() and use it in format(). Here's a formatter that supports a c specifier for compact output:
template<>
struct std::formatter<point> {
bool compact = false;
constexpr auto parse(std::format_parse_context& ctx) {
auto it = ctx.begin();
if (it != ctx.end() && *it == 'c') {
compact = true;
++it;
}
return it;
}
auto format(const point& p, std::format_context& ctx) const {
if (compact) {
return std::format_to(ctx.out(), "{},{}", p.x, p.y);
} else {
return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
}
};point p{3, 4};
std::format("{}", p); // "(3, 4)"
std::format("{:c}", p); // "3,4"Reusing Existing Formatters
Rather than implementing parsing from scratch, you can inherit from an existing formatter. This gives your type the same format specifiers as a built-in type for free:
template<>
struct std::formatter<point> : std::formatter<std::string> {
auto format(const point& p, std::format_context& ctx) const {
return std::formatter<std::string>::format(
std::format("({}, {})", p.x, p.y), ctx
);
}
};Now you can use string format specifiers like width and fill:
std::format("{:>20}", p); // " (3, 4)"