Lesson 8 of 9
Lesson 6.7 ended on a complaint: stream formatting is verbose and sticky. C++20's std::format
fixes both.
#include <format>
#include <iostream>
#include <string>
int main() {
std::string name = "Ada";
int age = 36;
std::cout << std::format("{} is {} years old\n", name, age);
return 0;
}
Ada is 36 years old
Each {} is replaced by the next argument, converted automatically. If you've used Python's
f-strings or str.format, this is deliberately the same design.
Why it beats streams
#include <format>
#include <iomanip>
#include <iostream>
#include <string>
int main() {
std::string item = "widget";
double price = 12.5;
// streams
std::cout << std::left << std::setw(10) << item
<< std::right << std::setw(8) << std::fixed << std::setprecision(2)
<< price << "\n";
// format
std::cout << std::format("{:<10}{:>8.2f}\n", item, price);
return 0;
}
widget 12.50
widget 12.50
The format version is one line instead of two, reads left to right in the order things appear,
and — crucially — isn't sticky. The stream version has now left std::fixed and
setprecision(2) switched on for everything printed afterwards.
The format spec
Inside the braces, after a colon:
#include <format>
#include <iostream>
int main() {
std::cout << std::format("[{:5}]\n", 42);
std::cout << std::format("[{:<5}]\n", 42);
std::cout << std::format("[{:>5}]\n", 42);
std::cout << std::format("[{:^5}]\n", 42);
std::cout << std::format("[{:*^7}]\n", 42);
std::cout << std::format("{:.3f}\n", 3.14159);
std::cout << std::format("{:.1f}\n", 2.55);
std::cout << std::format("{:b} {:o} {:x} {:X}\n", 255, 255, 255, 255);
std::cout << std::format("{:08.3f}\n", 3.14159);
return 0;
}
[ 42]
[42 ]
[ 42]
[ 42 ]
[**42***]
3.142
2.5
11111111 377 ff FF
0003.142
<>^— left, right, centre; a character before them is the fill..Nf— N decimal places.boxX— binary, octal, hex.0— pad with zeros.
Note {:.1f} on 2.55 gives 2.5, not 2.6. That's not a formatting bug — 2.55 isn't exactly
representable in binary (lesson 3.4), and the stored value is a hair below 2.55.
Positional arguments
#include <format>
#include <iostream>
int main() {
std::cout << std::format("{0} {1} {0}\n", "a", "b");
std::cout << std::format("{1}, {0}!\n", "world", "Hello");
return 0;
}
a b a
Hello, world!
Indices let you reuse an argument or reorder without touching the argument list — the reason translation systems want them.
Braces are checked at compile time
// WILL NOT COMPILE
#include <format>
#include <iostream>
int main() {
std::cout << std::format("{} {}\n", 42); // two slots, one argument
return 0;
}
This is the real advantage over C's printf. There, a mismatched format string is a run-time
crash or silent garbage; here the format string is checked while compiling, so the bug can't
ship.
To print a literal brace, double it: {{ and }}.
Building strings, not just printing
std::format returns a std::string, so it isn't tied to output:
#include <format>
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> lines;
for (int i = 1; i <= 3; ++i) {
lines.push_back(std::format("row {:>2}: {:.2f}", i, i * 1.5));
}
for (const std::string& line : lines) {
std::cout << line << "\n";
}
return 0;
}
row 1: 1.50
row 2: 3.00
row 3: 4.50
That's the common case — building a message to store, log, or return, which streams handle awkwardly.
Availability
std::format is C++20, but compiler support arrived late: GCC 13+, Clang 17+, MSVC 2019
16.10+. Check with g++ --version (lesson 1.2).
If yours is older you'll get fatal error: format: No such file or directory. The options are to
upgrade, use the {fmt} library it was based on, or fall back to streams. C++23 adds std::print,
which shortens std::cout << std::format(...) to just std::print(...).
Try it
Print a three-column table of three products — name left-aligned in 12 characters, quantity right-aligned in 5, price right-aligned in 10 with 2 decimals — plus a separator line.
Checkpoint
std::format("{}...", args)replaces each{}with the next argument and returns astd::string.- Format specs go after a colon:
{:<10},{:>8.2f},{:x},{:*^7}. - Unlike stream manipulators, formatting is not sticky.
- The format string is validated at compile time — unlike
printf. {{and}}print literal braces;{0}reuses arguments by index.- Requires GCC 13+, Clang 17+, or MSVC 2019 16.10+.
Questions about this lesson
Log in to ask a question.