std::format
std::format builds a string from a format string and a list of arguments. The placeholders are braces, the arguments are type-checked against them, and the result is a std::string the program can keep, compare or print.
It replaces the printf family and the stream manipulators for the common case: one function, one syntax, no state left behind on a stream. The format string is checked at compile time when it is a literal, which is where the last section's error comes from.
The format string is a compile-time constant when it is a literal, which is why the checks cost nothing at run time; a string assembled at run time is checked when it is used and may throw instead.
Placeholders
{} is replaced by the next argument, in order. The braces take any type the formatter knows: numbers, strings, characters, and the standard containers since C++23.
The program formats a string and an int, then a double and a char. The output is a std::string, which is why it can be printed with << or stored.
The index can be written explicitly, as in {0} and {1}, which allows an argument to appear twice. Mixing automatic and manual indices in one string is an error.
#include <format>
#include <iostream>
#include <string>
int main()
{
std::cout << std::format("{} has {} items\n", "cart", 3);
std::string line = std::format("{} and {}\n", 1.5, 'x');
std::cout << line;
std::cout << std::format("{0}-{1}-{0}\n", "a", "b");
}
g++ -std=c++23 -Wall -Wextra first.cpp -o first
./first
cart has 3 items
1.5 and x
a-b-a
Width, fill and alignment
The colon starts a format specifier. <, > and ^ align left, right and centre in the given width, the character before the alignment is the fill, and a precision after a dot rounds a floating point value.
The program prints three names in brackets with different alignments, a word centred with asterisks and a double with two decimals. The brackets make the width visible.
The same specifiers work in std::print and in the formatting of a table, which is the next section. The syntax is the one from Python's format, with the same alignment characters.
#include <format>
#include <iostream>
#include <string>
int main()
{
std::cout << std::format("[{:>8}] [{:<8}] [{:^8}]\n", "left", "right", "mid");
std::cout << std::format("[{:*^9}]\n", "fill");
std::cout << std::format("{:.2f}\n", 3.14159);
}
g++ -std=c++23 -Wall -Wextra spec.cpp -o spec
./spec
[ left] [right ] [ mid ]
[**fill***]
3.14
A formatted table
A table is a loop of format calls with fixed widths. The program prints a header and three rows, with the item names left-aligned in ten columns and the counts right-aligned in four.
The numbers line up because the width is part of the format string, not of the data. A name longer than the column still prints, and it pushes the rest of that row right, which is the usual trade of a fixed layout.
The rows are a vector of pairs, which lesson 060 introduced. A structured binding unpacks each row into the two format arguments.
#include <format>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
int main()
{
std::vector<std::pair<std::string, int>> items{{"apples", 3}, {"pears", 12}, {"figs", 7}};
std::cout << std::format("{:<10}{:>4}\n", "item", "qty");
for (const auto& [name, count] : items) {
std::cout << std::format("{:<10}{:>4}\n", name, count);
}
}
g++ -std=c++23 -Wall -Wextra table.cpp -o table
./table
item qty
apples 3
pears 12
figs 7
Numbers in other bases
The type specifier chooses the base: x for hexadecimal, X for upper case, b for binary and o for octal. The # flag adds the prefix, and a leading zero pads with zeros to the given width.
The program prints 255 in three bases, 5 in binary and 8 in octal, then a padded double and a signed one. The outputs show the width and the sign of each.
These specifiers replace the stream manipulators std::hex and std::setw for a single value. Lesson 066 covers the manipulators, which stay in the stream until they are changed.
#include <format>
#include <iostream>
int main()
{
std::cout << std::format("{:x} {:X} {:b} {:o}\n", 255, 255, 5, 8);
std::cout << std::format("{:#x} {:08.3f} {:+.1f}\n", 255, 3.14159, 2.5);
}
g++ -std=c++23 -Wall -Wextra numbers.cpp -o numbers
./numbers
ff FF 101 10
0xff 0003.142 +2.5
A specifier for the wrong type
The format string is checked at compile time when it is a literal, and d is the specifier for an integer. Passing a string literal with that specifier fails before the program is built.
The message says the call to the consteval format string constructor is not a constant expression, and the notes inside <format> show the check that failed. The line and the string are printed, which is enough to see the mismatch.
The fix is {} for a string and {:d} only for an integer. A format string built at run time cannot be checked at compile time, and the call throws std::format_error instead.
#include <format>
#include <iostream>
int main()
{
std::cout << std::format("{:d}\n", "text");
}
g++ -std=c++23 -Wall -Wextra mismatch.cpp -o mismatch
mismatch.cpp: In function 'int main()':
mismatch.cpp:6:30: error: call to consteval function 'std::basic_format_string<char, const char (&)[5]>("{:d}\012")' is not a constant expression
6 | std::cout << std::format("{:d}\n", "text");
| ^~~~~~~~
In file included from mismatch.cpp:1:
mismatch.cpp:6:30: in 'constexpr' expansion of 'std::basic_format_string<char, const char (&)[5]>("{:d}\012")'
/usr/include/c++/15.2.0/format:4810:19: in 'constexpr' expansion of '__scanner.std::__format::_Checking_scanner<char, char [5]>::std::__format::_Scanner<char>.std::__format::_Scanner<char>::_M_scan()'
/usr/include/c++/15.2.0/format:4504:30: in 'constexpr' expansion of '((std::__format::_Scanner<char>*)this)->std::__format::_Scanner<char>::_M_on_replacement_field()'
/usr/include/c++/15.2.0/format:4556:15: in 'constexpr' expansion of '((std::__format::_Scanner<char>*)this)->std::__format::_Scanner<char>::_M_format_arg(__id)'
/usr/include/c++/15.2.0/format:4648:33: in 'constexpr' expansion of '((std::__format::_Checking_scanner<char, char [5]>*)this)->std::__format::_Checking_scanner<char, char [5]>::_M_parse_format_spec<char [5]>(__id)'
/usr/include/c++/15.2.0/format:4662:40: in 'constexpr' expansion of '__f.std::formatter<char [5], char>::parse(((std::__format::_Checking_scanner<char, char [5]>*)this)->std::__format::_Checking_scanner<char, char [5]>::std::__format::_Scanner<char>.std::__format::_Scanner<char>::_M_pc.std::__format::_Scanner<char>::_Parse_context::std::basic_format_parse_context<char>)'
/usr/include/c++/15.2.0/format:2532:26: in 'constexpr' expansion of '((std::formatter<char [5], char>*)this)->std::formatter<char [5], char>::_M_f.std::__format::__formatter_str<char>::parse((* & __pc))'
/usr/include/c++/15.2.0/format:1322:48: error: call to non-'constexpr' function 'void std::__format::__failed_to_parse_format_spec()'
1322 | __format::__failed_to_parse_format_spec();
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
/usr/include/c++/15.2.0/format:264:3: note: 'void std::__format::__failed_to_parse_format_spec()' declared here
264 | __failed_to_parse_format_spec()
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Exercises
- Print a price with two decimals and a currency symbol using a format string.
- Print the numbers 0 to 7 in binary with a width of 3 and a leading zero.
- Build a table of three names and scores with the names left-aligned and the scores right-aligned.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What do the braces in
std::formatmark?Answer
A placeholder for an argument
Each
{}is filled from the argument list in order, and the specifier follows a colon. -
What does
{:04}do to an integer?Answer
It pads it to four digits with zeros
The zero is the fill character and the four is the minimum width.
-
What does
{:.2f}do?Answer
It prints two digits after the decimal point
The f is the fixed format and the 2 is the precision.
-
What happens when a specifier does not fit the argument type?
Answer
The call does not compile
The format string is checked at compile time against the argument types.
-
What does this program print?
#include <format> #include <iostream> int main() { std::cout << std::format("cart has {} items\n", 3); std::cout << std::format("{} and {}\n", 1.5, 'x'); std::cout << std::format("{0}-{1}-{0}", "a", "b"); }Answer
cart has 3 items / 1.5 and x / a-b-a
The placeholders take the arguments in order, and an index can repeat one.
-
What does this program print?
#include <format> #include <iostream> int main() { std::cout << std::format("[{:>5}] [{:<5}] [{:^5}]\n", "left", "right", "mid"); std::cout << std::format("[{:*<9}]\n", "fill"); std::cout << std::format("{:.3}\n", 3.14159); }Answer
the aligned words, then fill*****, then 3.14
The alignment, the fill character and the precision each produce the piece shown.
-
What does this program print?
#include <format> #include <iostream> int main() { std::cout << std::format("{:8}{:>5}\n", "item", "qty"); std::cout << std::format("{:8}{:>5}\n", "apples", 3); std::cout << std::format("{:8}{:>5}\n", "pears", 12); }Answer
item qty / apples 3 / pears 12
The two columns are eight and five characters wide, and the numbers are right-aligned.
-
What does this program print?
#include <format> #include <iostream> int main() { std::cout << std::format("{:x} {:X} {:b} {:o}\n", 255, 255, 5, 8); std::cout << std::format("{:#x} {:08.3f} {:+.1f}\n", 255, 3.14159, 2.5); }Answer
ff FF 101 10 / 0xff 0003.142 +2.5
The base specifiers print the first line, and the alternate form, width and sign print the second.
-
What does the compiler report for this call?
#include <format> #include <iostream> int main() { std::cout << std::format("{:d}\n", "text"); }Answer
the format string is not a constant expression for these arguments
The specifier is checked when the format string is compiled, and a string does not fit :d.
-
Why is std::format type-safe compared with printf?
Answer
The format string and the arguments are checked at compile time
The compiler sees the format string and the argument types together.
The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.
Article author: Arthur Isaev