Skip to content

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:

cpp
#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:

cpp
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:

cpp
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);
        }
    }
};
cpp
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:

cpp
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:

cpp
std::format("{:>20}", p);  // "              (3, 4)"

See Also