Formatting Text with std::format
C++20's std::format builds strings from a template with {} placeholders — type-safe like streams, concise like printf, and without sticky stream state. It's the modern way to format numbers, align tables and build messages.
Basic use
#include <format> #include <iostream> #include <string> int main() { std::string name = "Ada"; int age = 36; double height = 1.74; std::string s = std::format("{} is {} years old and {} m tall", name, age, height); std::cout << s << '\n'; std::cout << std::format("{1} before {0}", "second", "first") << '\n'; // positional arguments }
Ada is 36 years old and 1.74 m tall first before second
Each {} takes the next argument; {0}, {1} pick arguments by position. Format strings are checked at compile time — a placeholder without an argument is a compile error.
Format specifications
After a colon inside the braces, a small language controls the output: {:[fill][align][sign][width][.precision][type]}.
#include <format> #include <iostream> #include <numbers> int main() { double pi = std::numbers::pi; std::cout << std::format("[{:.2f}] [{:10.3f}] [{:<10.1f}] [{:^10.1f}]\n", pi, pi, pi, pi); std::cout << std::format("[{:5}] [{:<5}] [{:05}] [{:+}] [{:*^9}]\n", 42, 42, 42, 42, 42); std::cout << std::format("hex {:x}, HEX {:#X}, octal {:o}, binary {:b}, {:08b}\n", 255, 255, 8, 5, 5); std::cout << std::format("{:e} {:g} {:.3g}\n", 123456.789, 0.0001234, 123456.789); std::cout << std::format("{:>8} | {:<8} | {:^8}|\n", "right", "left", "mid"); }
[3.14] [ 3.142] [3.1 ] [ 3.1 ] [ 42] [42 ] [00042] [+42] [***42****] hex ff, HEX 0XFF, octal 10, binary 101, 00000101 1.234568e+05 0.0001234 1.23e+05 right | left | mid |
| Spec | Meaning |
|---|---|
< > ^ | Align left, right, centre (numbers default right, text left) |
| A character before the alignment | Fill character, e.g. {:*^9} |
+ | Always show the sign |
0 before the width | Pad numbers with zeros |
.n | Precision: decimals for f, significant digits for g, max characters for text |
f e g | Fixed, scientific, general floating-point form |
d x X o b | Decimal, hex, upper hex, octal, binary (# adds 0x/0b) |
Tables
#include <format> #include <iostream> #include <string> #include <vector> struct Line { std::string item; int qty; double price; }; int main() { std::vector<Line> lines{{"Notebook", 4, 3.5}, {"Pen", 10, 1.2}, {"Backpack", 1, 39.9}}; std::cout << std::format("{:<10} {:>4} {:>8} {:>9}\n", "Item", "Qty", "Price", "Total"); double sum = 0; for (const auto& [item, qty, price] : lines) { std::cout << std::format("{:<10} {:>4} {:>8.2f} {:>9.2f}\n", item, qty, price, qty * price); sum += qty * price; } std::cout << std::format("{:-<34}\n{:<24} {:>9.2f}\n", "", "Total", sum); }
Item Qty Price Total Notebook 4 3.50 14.00 Pen 10 1.20 12.00 Backpack 1 39.90 39.90 ---------------------------------- Total 65.90
Dynamic width and precision
#include <format> #include <iostream> int main() { for (int width = 4; width <= 8; width += 2) { std::cout << std::format("[{:>{}}]\n", 42, width); } std::cout << std::format("{:.{}f}\n", 2.718281828, 3); }
[ 42] [ 42] [ 42] 2.718
Formatting your own types
Specialise std::formatter to make your type usable in std::format. The simplest way is to build on an existing formatter:
#include <format> #include <iostream> struct Point { double x, y; }; 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); } }; int main() { Point p{3, 4.5}; std::cout << std::format("point {} | padded [{:>14}]\n", p, p); }
point (3, 4.5) | padded [ (3, 4.5)]
format_to and other helpers
std::format_to writes into an existing buffer or output iterator instead of creating a new string, and std::formatted_size tells you how long the result will be. In C++23, std::print and std::println format straight to the console (lesson 50).
#include <format> #include <iostream> #include <iterator> #include <string> int main() { std::string log; for (int i = 1; i <= 3; ++i) { std::format_to(std::back_inserter(log), "step {} of 3; ", i); } std::cout << log << '\n'; }
step 1 of 3; step 2 of 3; step 3 of 3;
Try it yourself
Print a temperature conversion table from −10 to 40 °C in steps of 10, with aligned columns and one decimal place.
Celsius | Fahr.
-10 | 14.0
0 | 32.0
10 | 50.0
20 | 68.0
30 | 86.0
40 | 104.0