Site

Formatting — std::format and ostream

Tutorial S10.0  •  C++ / Learn / StdLib

S10.0 What This Teaches

C++ has two main approaches to text formatting. This tutorial covers:

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

ApproachHeaderType-safeNotes
std::format<format>YesC++20; preferred for new code
ostream <<<iostream> <iomanip>YesAll C++ versions; manipulator state persists
printf<cstdio>NoC 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

TermMeaning
std::formatC++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::fixedFormat floating-point in fixed notation; persists
std::hex / std::decPrint integers in hex / decimal; persists until changed
printfC-style formatted output; not type-safe; avoid in new C++ code