S10.0 What This Teaches
C++ has two main approaches to text formatting. This tutorial covers:
std::format (C++20) — Python-style format strings
- Width, alignment, fill, precision, and numeric bases
ostream manipulators from <iomanip>
printf — the C-style approach (legacy)
- When to use each approach
S10.1 std::format Basics
#include <format>
#include <iostream>
std::string s = std::format("Hello, {}!", "world");
std::cout << s << "\n"; // Hello, world!
int n = 42;
double pi = 3.14159;
std::cout << std::format("n={}, pi={}\n", n, pi); // n=42, pi=3.14159
{} is a replacement field. Arguments are substituted in order.
std::format returns a std::string; pass it to
std::cout or store it. C++23 adds std::print
which writes directly.
S10.2 Format Specifiers
double pi = 3.14159265358979;
// precision
std::cout << std::format("{:.2f}\n", pi); // 3.14
std::cout << std::format("{:.6f}\n", pi); // 3.141593
// width and alignment
std::cout << std::format("{:>10}\n", "right"); // right
std::cout << std::format("{:<10}\n", "left"); // left
std::cout << std::format("{:^10}\n", "mid"); // mid
// fill character
std::cout << std::format("{:0>8d}\n", 42); // 00000042
std::cout << std::format("{:-^10}\n", "hi"); // ----hi----
// numeric bases
std::cout << std::format("{:#x}\n", 255); // 0xff
std::cout << std::format("{:#b}\n", 10); // 0b1010
std::cout << std::format("{:#o}\n", 8); // 010
S10.3 ostream Manipulators
#include <iomanip>
// width (applies to next output only)
std::cout << std::setw(10) << std::right << "right" << "\n"; // right
std::cout << std::setw(10) << std::left << "left" << "\n"; // left
// fill character (persists)
std::cout << std::setfill('0') << std::setw(6) << 42 << "\n"; // 000042
std::cout << std::setfill(' '); // reset fill to space
// floating point
std::cout << std::fixed << std::setprecision(4) << 3.14159 << "\n"; // 3.1416
// numeric base (persists until changed)
std::cout << std::hex << 255 << "\n"; // ff
std::cout << std::dec; // reset to decimal
Manipulators that persist (setfill, hex,
fixed, setprecision) change the stream's
state for all subsequent output. Reset them after use to avoid
surprising behavior elsewhere.
S10.4 printf (Legacy)
#include <cstdio>
std::printf("Hello, %s!\n", "world");
std::printf("n=%d, pi=%.4f\n", 42, 3.14159);
std::printf("%10s | %-10s\n", "right", "left");
printf has no type safety — passing the wrong type for a
format specifier is undefined behavior. Use it only when interfacing with
C code or legacy systems. Prefer std::format for new code.
S10.5 Approach Comparison
| Approach | Header | Type-safe | Notes |
| std::format | <format> | Yes | C++20; preferred for new code |
| ostream << | <iostream> <iomanip> | Yes | All C++ versions; manipulator state persists |
| printf | <cstdio> | No | C legacy; avoid in new C++ code |
S10.6 Example - All Together
// Formatting - std::format (C++20), ostream manipulators, printf comparison.
#include <iostream>
#include <format>
#include <iomanip>
#include <cstdio>
int main() {
double pi = 3.14159265358979;
std::cout << "--- std::format ---\n";
std::cout << std::format("pi = {:.4f}\n", pi);
std::cout << std::format("{:>10} | {:-^10} | {:<10}\n", "right", "mid", "left");
std::cout << std::format("{:#x} {:#b} {:#o}\n", 255, 10, 8);
std::cout << "\n--- manipulators ---\n";
std::cout << std::fixed << std::setprecision(4) << pi << "\n";
std::cout << std::setw(10) << std::right << "right" << "\n";
std::cout << std::hex << std::showbase << 255 << "\n";
std::cout << std::dec;
std::cout << "\n--- printf ---\n";
std::printf("pi = %.4f\n", pi);
std::printf("%10s | %-10s\n", "right", "left");
return 0;
}
--- std::format ---
pi = 3.1416
right | ---mid---- | left
0xff 0b1010 010
--- manipulators ---
3.1416
right
0xff
--- printf ---
pi = 3.1416
right | left
S10.7 Exercise
Exercise
- Use
std::format to print a table of squares and cubes
for 1 through 10, right-aligned in columns of width 6.
- Use
ostream manipulators to print the same table.
Note the extra effort required to reset state.
- Write a function
hex_dump(const std::vector<uint8_t>&)
that prints each byte as a two-digit hex value separated by spaces,
using std::format.
S10.8 Common Mistakes
Persistent manipulator state polluting later output
std::cout << std::hex << 255 << "\n"; // ff
std::cout << 42 << "\n"; // 2a (still hex!)
Reset std::hex with std::dec after use.
std::format avoids this entirely — each call is
independent.
printf type mismatch
std::printf("%d\n", 3.14); // undefined behavior: %d expects int, got double
Use std::format or ostream to get compile-time type
checking.
std::format not available before C++20
Confirm your compiler targets C++20 (-std=c++20 for
GCC/Clang, /std:c++20 for MSVC). For older standards,
use ostream manipulators or the {fmt} library.
S10.9 Key Terms
| Term | Meaning |
| std::format | C++20 function returning a formatted string; type-safe, no side effects on stream state |
| {} | Replacement field in a format string; {} auto-selects type, {:specifier} customizes |
| std::setw(n) | Set minimum field width for the next output item only |
| std::setfill(c) | Set fill character used when output is shorter than setw; persists |
| std::setprecision(n) | Set decimal precision for floating-point output; persists |
| std::fixed | Format floating-point in fixed notation; persists |
| std::hex / std::dec | Print integers in hex / decimal; persists until changed |
| printf | C-style formatted output; not type-safe; avoid in new C++ code |