Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

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.
  • b o x X — 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 a std::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.