10 std::format and std::print
In Chapter 9, you learned how to read and write data using streams. Formatting that output — aligning columns, controlling decimal places, padding with characters — used to require verbose stream manipulators. C++20 introduced std::format, and C++23 added std::print and std::println, giving you clean, readable formatting in one step. In this chapter, you will learn how to use these modern formatting tools.
10.1 std::format
Before C++20, formatting output with std::cout could be awkward. Mixing text and values with lots of << operators gets hard to read quickly.
C++20 introduced std::format in the <format> header. It uses format strings with {} placeholders, similar to Python’s f-strings or C’s printf.
std::string format(format_string fmt, Args... args);std::format takes a format string and its arguments, and returns a std::string.
#include <format>
#include <iostream>
#include <string>
int main() {
std::string artist = "Santana";
int year = 1999;
std::string msg = std::format("{} --- Smooth ({})", artist, year);
std::cout << msg << std::endl;
return 0;
}Output:
Santana --- Smooth (1999)std::format returns a std::string. Each {} is replaced by the next argument, in order. This is called implicit argument numbering.
10.1.1 Implicit vs. Indexed Arguments
You can also use indexed arguments by putting a number inside the braces. The number refers to the zero-based position of the argument:
std::string msg = std::format("{1} --- {0} ({2})",
"Santana", "Smooth", 1999);
// "Smooth --- Santana (1999)"Indexed arguments let you reorder or reuse arguments without changing the argument list.
std::format("{0}, {0}, {0}!", "yeah"); // "yeah, yeah, yeah!"
Trap: You cannot mix implicit {} and indexed {0} in the same format string. std::format("{},{1}", 1, "hi") is an error. Pick one style and use it consistently within each format string.
10.1.2 Format Specifiers
You can control how values are formatted by adding specifiers inside the braces, after a :. The full grammar of the spec, in the order the pieces must appear, is:
[[fill]align][sign][#][0][width][.prec][type]Every piece is optional, but if you include more than one they have to follow this order. The pieces are introduced one by one in the rest of the section.
| Piece | What it controls |
|---|---|
fill | character used for padding (default: space). Any character except {/}. |
align | < left, > right, ^ centered. |
sign | + always show, - only on negatives, space pad positives. |
# | alternate form (e.g. 0x prefix for hex). |
0 | pad numeric values with leading zeros (implies right-align). |
width | minimum field width. |
.prec | digits after the . for f floats, else significant digits; max length for strings. |
type | d, x, o, b, f, s, p, etc. |
Width and alignment:
// Right-align in a field of 10 characters
std::format("{:>10}", "hola"); // " hola"
// Left-align in a field of 10 characters
std::format("{:<10}", "hola"); // "hola "
// Center in a field of 10 characters
std::format("{:^10}", "hola"); // " hola "Fill characters:
std::format("{:*>10}", "hola"); // "******hola"
std::format("{:-^20}", "Smooth"); // "-------Smooth-------"Sign:
The sign specifier controls how positive numbers are displayed:
std::format("{:+}", 42); // "+42" (always show sign)
std::format("{:-}", 42); // "42" (`-` only on negatives)
std::format("{: }", 42); // " 42" (space where + would go)
Alternate form (#) and zero-padding (0):
The # flag shows a prefix that identifies the number base. The 0 flag pads with zeros instead of spaces:
std::format("{:#x}", 255); // "0xff" (hex with 0x prefix)
std::format("{:#b}", 10); // "0b1010" (binary with 0b prefix)
std::format("{:05}", 42); // "00042" (zero-padded to width 5)
std::format("{:#010x}", 255); // "0x000000ff" (combined)Number formatting:
std::format("{:d}", 42); // "42" (decimal integer)
std::format("{:x}", 255); // "ff" (hexadecimal)
std::format("{:o}", 8); // "10" (octal)
std::format("{:b}", 10); // "1010" (binary)Floating-point precision:
std::format("{:.2f}", 3.14159); // "3.14"
std::format("{:.4f}", 2.5); // "2.5000"
std::format("{:10.2f}", 3.14); // " 3.14"Here is a more complete example:
#include <format>
#include <iostream>
int main() {
std::cout << std::format("{:<20} {:>5} {:>8}",
"Song", "Year", "Score") << std::endl;
std::cout << std::format("{:<20} {:>5} {:>8.1f}",
"Wonderwall", 1995, 9.5) << std::endl;
std::cout << std::format("{:<20} {:>5} {:>8.1f}",
"Jumper", 1997, 9.8) << std::endl;
std::cout << std::format("{:<20} {:>5} {:>8.1f}",
"Say My Name", 1999, 8.7) << std::endl;
return 0;
}Output:
Song Year Score
Wonderwall 1995 9.5
Jumper 1997 9.8
Say My Name 1999 8.7
Tip: std::format is much easier to read than chaining << operators with std::setw and std::setprecision. If your compiler supports C++20 or later, prefer std::format for any non-trivial formatting.
10.2 std::print and std::println
C++23 took things one step further with std::print and std::println in the <print> header. These combine formatting and printing into a single call.
void print(format_string fmt, Args... args);
void println(format_string fmt, Args... args);#include <print>
int main() {
std::println("You get what you give, don't let go");
std::print("Track {:d}: {}", 1, "You Get What You Give");
std::println();
double score = 9.5;
std::println("Rating: {:.1f}/10", score);
return 0;
}Output:
You get what you give, don't let go
Track 1: You Get What You Give
Rating: 9.5/10std::println prints a formatted string followed by a newline. std::print prints without a trailing newline.
These are the modern replacements for std::cout <<. They are shorter to write, easier to read, and handle formatting in one step.
Wut: std::print and std::println require C++23 support. Not all compilers support them yet. If your compiler does not have <print>, you can use std::format with std::cout to achieve the same result.
10.3 Putting It All Together
Here is a program that uses the file I/O from Chapter 9 along with the formatting tools from this chapter:
#include <fstream>
#include <format>
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::ofstream outfile("puntuaciones.txt");
if (!outfile) {
std::cerr << "No puedo abrir el archivo" << std::endl;
return 1;
}
outfile << "Wonderwall 9.5" << std::endl;
outfile << "Jumper 9.8" << std::endl;
outfile << "SayMyName 8.7" << std::endl;
outfile.close();
std::ifstream infile("puntuaciones.txt");
if (!infile) {
std::cerr << "Could not open file" << std::endl;
return 1;
}
std::string line;
std::cout << std::format("{:<15} {:>6}", "Song", "Score")
<< std::endl;
std::cout << std::string(22, '-') << std::endl;
while (std::getline(infile, line)) {
std::istringstream iss(line);
std::string song;
double score;
iss >> song >> score;
std::cout << std::format("{:<15} {:>6.1f}", song, score)
<< std::endl;
}
infile.close();
return 0;
}Output:
Song Score
----------------------
Wonderwall 9.5
Jumper 9.8
SayMyName 8.710.4 Try It: Formatting Starter
Here is a program that exercises the formatting tools from this chapter. Type it in, compile it, and experiment:
#include <format>
#include <print>
#include <string>
int main() {
// std::format builds a std::string;
// std::println prints it with a newline
std::string header = std::format("{:<10} {:>6} {:>9}",
"Item", "Qty", "Price");
std::println("{}", header);
std::println("{:-^27}", "");
// width, alignment, and floating-point precision
std::println("{:<10} {:>6} {:>9.2f}", "pencil", 12, 0.5);
std::println("{:<10} {:>6} {:>9.2f}", "notebook", 3, 2.25);
std::println("{:<10} {:>6} {:>9.2f}", "backpack", 1, 24.999);
// fill characters and centering
std::println("{:*^27}", " receipt ");
// hex and binary, zero-padded with base prefixes
int code = 200;
std::println("decimal: {:d}", code);
std::println("hex: {:#06x}", code);
std::println("binary: {:#010b}", code);
// std::print does not add a newline
std::print("printing");
std::print("...");
std::println("done");
return 0;
}Output:
Item Qty Price
---------------------------
pencil 12 0.50
notebook 3 2.25
backpack 1 25.00
********* receipt *********
decimal: 200
hex: 0x00c8
binary: 0b11001000
printing...doneThings to try:
- Change the column alignments (
<,>,^) and widths and watch the table reshape itself. - Change the price precision from
.2fto.1for.4fand see how24.999rounds. - Swap the
-and*fill characters for something else, like=or.. - Change
codeto other values and check that the hex and binary lines match what you compute by hand. - Remove the
0from{:#06x}and{:#010b}and see what padding you lose. - Rewrite one
std::printlnline using indexed arguments ({0},{1}, …), then try mixing in a plain{}and read the compiler error you get.
10.5 Key Points
std::format(C++20) uses{}placeholders to produce formatted strings.- Use
{0},{1}, etc., to reorder or reuse arguments. You cannot mix implicit{}and indexed{0}in the same format string. std::printandstd::println(C++23) combine formatting and output in one step.- Format specifiers control width, alignment, precision, and base (e.g.,
{:>10.2f},{:x}). - Fill characters, alignment (
<,>,^), and number bases (d,x,o,b) give you fine-grained control. std::formatreturns astd::string;std::printandstd::printlnwrite directly to the output.
10.6 Exercises
What does
std::format("{:>8.2f}", 3.1)produce? How many characters wide is the result?Why might you prefer
std::formatover chaining<<operators withstd::cout? Give at least two reasons.What is the difference between
std::printandstd::println?What does
std::format("{:*^20}", "Hola")produce?What is wrong with this code?
std::string result = std::format("{} scored {1} points", name, score);Write a program that asks the user for three song names and three scores (as doubles), writes them to a file called
rankings.txt(one song and score per line), then reads the file back and prints a formatted table with columns for song name and score, right-aligning the scores to one decimal place.What does this print?
std::println("{1} - {0} ({2})", "Backstreet Boys", "I Want It That Way", 1999);Now change every
{0}/{1}/{2}to plain{}and predict the output. What is the rule about mixing indexed and implicit placeholders in the same format string?Calculation: What does each of these
std::formatcalls produce?std::format("{:+d}", 42) std::format("{:+d}", -42) std::format("{: d}", 42) std::format("{:05d}", 42) std::format("{:+06d}", -42)For each one, write down the exact characters in the resulting string, including any spaces or zeros.
What does this print?
int n = 255; std::println("{:#x}", n); std::println("{:#o}", n); std::println("{:#b}", n); std::println("{:08b}", n);What does the
#flag do, and what does the08in the last line do?What does this print?
std::string title = "Smells Like Teen Spirit"; std::println("[{:.5}]", title); std::println("[{:<10.5}]", title); std::println("[{:>10.5}]", title);For string arguments, what does the precision (
.5) mean? How is that different from precision on adouble?Write a program that takes three integers, formats them into a single
std::stringusingstd::format, and prints each integer in three different ways:- decimal in a 6-character field, right-aligned
- hexadecimal with the
0xprefix and zero-padded to 8 hex digits - binary with the
0bprefix, zero-padded to 16 bits
Use a single
std::formatcall per row so you practice combining width, fill, and base specifiers in the same format string.