std::format and std::print
C++20 std::format and C++23 std::print — type-safe formatted output with compile-time checking, custom formatters, and efficient direct printing.
std::formatsince C++20A compile-time-checked, type-safe function that formats arguments into a string according to a format specifier, replacing sprintf and ostringstream for most use cases.
Overview
std::format (C++20) formats values into a std::string with compile-time validation of the format string and argument count. std::print and std::println (C++23) perform the same formatting but write directly to an output stream, eliminating the intermediate string allocation.
Key advantages over legacy approaches:
- Compile-time safety: Format specifiers and argument counts are checked at compile time (if the format string is a string literal)
- Type-safe: Unlike
sprintf, format specifiers cannot mismatch their arguments - Locale-independent by default: No
setlocaleside effects; use:Lto enable locale-aware formatting - Performance: Significantly faster than
sprintfandostringstreamfor typical formatting tasks - Familiar syntax: Similar to Python's
str.format()and JavaScript template literals
Syntax
Basic Format String
#include <format> // C++20
#include <print> // C++23
std::string s = std::format(format_string, arg0, arg1, ...);
std::print(format_string, arg0, arg1, ...); // C++23
std::println(format_string, arg0, arg1, ...); // C++23; adds newlineFormat Specification Mini-Language
{[arg-id][:[fill][align][sign][#][0][width][grouping][.precision][type]]}- arg-id:
0,1,2, ... or omitted for sequential - fill/align: padding character and alignment (
<,>,^for left, right, center) - sign:
+(always show),-(negative only, default), space (space for positive) #: alternate form (prefix0x,0b,0ofor integers; decimal point for floats)0: zero-pad (shorthand forfill='0'withalign='0')- width: minimum field width
- grouping:
,for thousands separators (C++26; use custom formatter for C++20–23) - precision: decimal places (floats) or max width (strings)
- type:
d,b,o,x,X(integers);f,e,E,g,G(floats);s(strings);c(char);B,b(bool)
Examples
Positional and Sequential Arguments
// Sequential (automatic indexing)
std::format("{} + {} = {}", 1, 2, 3); // "1 + 2 = 3"
// Positional indexing
std::format("{2} {0} {1}", "a", "b", "c"); // "c a b"
// Reuse arguments
std::format("{0} {0}", "hello"); // "hello hello"Integer Formatting
std::format("{}", 42); // "42"
std::format("{:d}", 42); // "42" (decimal, explicit)
std::format("{:b}", 42); // "101010" (binary; C++20)
std::format("{:o}", 42); // "52" (octal)
std::format("{:x}", 255); // "ff" (hex lowercase)
std::format("{:X}", 255); // "FF" (hex uppercase)
std::format("{:#x}", 0xFF); // "0xff" (alternate form)
std::format("{:#b}", 42); // "0b101010" (alternate form)
std::format("{:+d}", 42); // "+42" (always show sign)
std::format("{:010d}", 42); // "0000000042" (zero-padded width 10)
std::format("{:_^10}", "x"); // "____x_____" (custom fill char)Float Formatting
std::format("{}", 3.14159); // "3.14159" (shortest round-trip)
std::format("{:f}", 3.14); // "3.140000" (fixed, 6 decimals)
std::format("{:.2f}", 3.14159); // "3.14" (fixed, 2 decimals)
std::format("{:e}", 3.14); // "3.140000e+00" (scientific)
std::format("{:.2e}", 3.14); // "3.14e+00"
std::format("{:g}", 3.14); // "3.14" (shortest of f or e)
std::format("{:g}", 1e15); // "1e+15" (switches to e for large values)
std::format("{:8.3f}", 3.14); // " 3.140" (width 8, 3 decimals, right-aligned)
std::format("{:<8.3f}", 3.14); // "3.140 " (left-aligned)
std::format("{:+.1f}", 3.14); // "+3.1" (show sign)String and Character Formatting
std::format("{}", "hello"); // "hello"
std::format("{:10}", "hi"); // "hi " (left-aligned, width 10)
std::format("{:>10}", "hi"); // " hi" (right-aligned)
std::format("{:.5}", "hello world"); // "hello" (truncate to 5 chars)
std::format("{:>10.3}", "hello"); // " hel" (right-align, width 10, max 3 chars)
std::format("{:c}", 65); // "A" (int to char)
std::format("{:b}", true); // "true" (boolean)
std::format("{:s}", true); // "1" (boolean as integer; avoid, use :b or :d)std::print and std::println (C++23)
#include <print>
std::print("Value: {}\n", 42); // writes to stdout, no newline added
std::println("Value: {}", 42); // writes to stdout, adds '\n'
std::println(std::cerr, "Error: {}", msg); // write to stderr
std::println(stdout, "C-style: {}", 42); // write to FILE*
// Direct printing is faster than std::format + std::cout
// because it avoids intermediate string allocationRuntime Format Strings
// std::format checks format string at compile time only if it's a string literal
std::string user_format = "{} + {} = {}";
std::string result = std::vformat(user_format, std::make_format_args(1, 2, 3));Custom Formatters
struct Point { double x, y; };
template<>
struct std::formatter<Point> {
bool use_polar = false;
constexpr auto parse(std::format_parse_context& ctx) {
auto it = ctx.begin();
if (it != ctx.end() && *it == 'p') {
use_polar = true;
++it;
}
return it;
}
auto format(const Point& p, std::format_context& ctx) const {
if (use_polar) {
double r = std::sqrt(p.x * p.x + p.y * p.y);
double theta = std::atan2(p.y, p.x);
return std::format_to(ctx.out(), "({:.2f}∠{:.2f}°)", r, theta * 180 / M_PI);
} else {
return std::format_to(ctx.out(), "({:.2f}, {:.2f})", p.x, p.y);
}
}
};
Point p{3.0, 4.0};
std::println("{}", p); // "(3.00, 4.00)"
std::println("{:p}", p); // "(5.00∠53.13°)"Best Practices
-
Prefer
std::printoverstd::format+std::cout(C++23): Direct output is faster and avoids intermediate string allocation. -
Use compile-time format strings: String literals enable compile-time format checking. For runtime strings, use
std::vformatand document why. -
Avoid locale-aware formatting unless needed: The default locale-independent formatting is faster and more predictable. Use
:Lonly when explicitly required for user-facing output. -
For custom types, specialize
std::formatter: More efficient and consistent than operator overloading withoperator<<. -
Specify precision for floats explicitly: Default precision can vary; use
.2for.3gto be clear about output format.
Common Pitfalls
Compile-time checking requires string literal format:
std::format("{}", 42); // ✓ OK, compiles with check
std::string fmt = "{}";
std::vformat(fmt, std::make_format_args(42)); // ✓ OK, but no compile-time checkFormat specifier mismatch is a compile error:
// std::format("{:d}", "hello"); // ✗ compile error: d is not valid for stringBoolean formatting defaults to 1/0 with {:d}:
std::format("{}", true); // "true" (default)
std::format("{:d}", true); // "1" (integer format)
std::format("{:b}", true); // "true" (explicit boolean)Precision truncates strings, not rounds:
std::format("{:.3}", "hello"); // "hel" (truncates to 3 characters)
std::format("{:.2f}", 3.999); // "4.00" (rounds for floats only)Locale-independent by default—use :L for locale-aware output:
std::format("{:,}", 1000000); // "1000000" (no separator, format ignores locale)
std::format("{:L}", 1000000); // locale-aware, e.g., "1,000,000" in en_US (C++26)See Also
std::string_view— lightweight non-owning string reference (C++17)std::stringstream— older alternative; slower and more verbosefmtlibrary — predecessor tostd::format; backport available for older C++ standardssprintf,printf— C-style formatting; unsafe and locale-dependent