Step 2 of 7
Formatting your own types
std::format knows the built-in types and strings. To make it understand your type, you specialize std::formatter for it, the same way you taught streams with operator<<.
The easiest way is to reuse an existing formatter: inherit from std::formatter<std::string> (which already parses width, alignment and fill), build your text, and pass it to the base class's format. Then every spec that works for strings works for your type too.
#include <format>
#include <iostream>
#include <string>
struct Temp {
double celsius;
};
template <>
struct std::formatter<Temp> : std::formatter<std::string> {
auto format(const Temp& t, std::format_context& ctx) const {
std::string text = std::format("{:.1f}C/{:.0f}F", t.celsius, t.celsius * 9 / 5 + 32);
return std::formatter<std::string>::format(text, ctx);
}
};
int main() {
Temp today{21.5};
std::cout << std::format("today: {}\n", today);
std::cout << std::format("[{:>14}]\n", today); // width and alignment come for free
std::cout << std::format("[{:-<14}]\n", Temp{-3});
}
today: 21.5C/71F
[ 21.5C/71F]
[-3.0C/27F-----]
The text 21.5C/71F is 9 characters, so {:>14} pads it with 5 spaces on the left, and {:-<14} pads with dashes on the right.
How it works
template <> struct std::formatter<Temp>is a full specialization, like the ones in the templates module.formatreceives your object and a context (where the output goes). It builds a plain string with an innerstd::formatcall and hands that to the base class, which applies the width, alignment and fill from the placeholder.- The function must be
const, and returningautoavoids spelling out the iterator type.
Your task: cents to dollars
Split the cents with / 100 and % 100, and zero-pad the cents part to two digits: std::format("${}.{:02}", c / 100, c % 100). Then pass the result to the base formatter so {:>10} and friends work on Money too.
Your turn: write a formatter for Money (an amount in cents, never negative here) that shows it as dollars: Money{1205} is $12.05, Money{7} is $0.07.