Modern String Formatting
C++20’s std::format provides Python-style string formatting that’s type-safe, efficient, and more intuitive than iostream or printf.
Basic Syntax
#include <format>
#include <string>
std::string name = "Alice";
int age = 30;
// C++20 format
std::string msg = std::format("Hello, {}! You are {} years old.", name, age);
// "Hello, Alice! You are 30 years old."
Comparison with Alternatives
// printf: not type-safe, no std::string support
printf("Hello, %s! You are %d years old.\n", name.c_str(), age);
// iostream: verbose, awkward
std::ostringstream oss;
oss << "Hello, " << name << "! You are " << age << " years old.";
std::string msg = oss.str();
// std::format: clean and type-safe
auto msg = std::format("Hello, {}! You are {} years old.", name, age);
Positional Arguments
Reference arguments by position:
std::format("{0} {1} {0}", "Hello", "World"); // "Hello World Hello"
std::format("{1} {0}", "World", "Hello"); // "Hello World"
Format Specifications
Integers
int num = 42;
std::format("{}", num); // "42"
std::format("{:d}", num); // "42" (decimal)
std::format("{:x}", num); // "2a" (hexadecimal lowercase)
std::format("{:X}", num); // "2A" (hexadecimal uppercase)
std::format("{:o}", num); // "52" (octal)
std::format("{:b}", num); // "101010" (binary)
std::format("{:06d}", num); // "000042" (zero-padded, width 6)
std::format("{:+d}", num); // "+42" (show sign)
std::format("{: d}", num); // " 42" (space for positive)
Floating-Point
double pi = 3.14159265359;
std::format("{}", pi); // "3.14159265359"
std::format("{:.2f}", pi); // "3.14" (2 decimal places)
std::format("{:.5f}", pi); // "3.14159"
std::format("{:e}", pi); // "3.141593e+00" (scientific)
std::format("{:E}", pi); // "3.141593E+00"
std::format("{:g}", pi); // "3.14159" (general format)
std::format("{:10.2f}", pi); // " 3.14" (width 10)
std::format("{:010.2f}", pi); // "0000003.14" (zero-padded)
Strings
std::string text = "Hello";
std::format("{}", text); // "Hello"
std::format("{:10}", text); // "Hello " (width 10, left-aligned)
std::format("{:>10}", text); // " Hello" (right-aligned)
std::format("{:^10}", text); // " Hello " (centered)
std::format("{:*<10}", text); // "Hello*****" (fill with *)
std::format("{:.3}", text); // "Hel" (truncate to 3)
Alignment and Fill
int n = 42;
std::format("{:<5}", n); // "42 " (left-aligned, width 5)
std::format("{:>5}", n); // " 42" (right-aligned)
std::format("{:^5}", n); // " 42 " (centered)
std::format("{:*>5}", n); // "***42" (fill with *, right-aligned)
std::format("{:0>5}", n); // "00042" (zero-padded)
Type-Specific Formatting
Pointers
int* ptr = &value;
std::format("{}", ptr); // "0x7ffc12345678"
std::format("{:p}", ptr); // "0x7ffc12345678"
Booleans
bool flag = true;
std::format("{}", flag); // "true"
std::format("{:s}", flag); // "true" (string representation)
std::format("{:d}", flag); // "1" (numeric representation)
Characters
char ch = 'A';
std::format("{}", ch); // "A"
std::format("{:d}", ch); // "65" (ASCII value)
Compile-Time Format String Checking
Format strings are checked at compile-time:
int n = 42;
std::format("{}", n); // OK
std::format("{:d}", n); // OK
std::format("{:f}", n); // Compile error: can't format int as float
std::format("{} {}", n); // Compile error: too few arguments
Custom Type Formatting
Extend formatting for your types:
struct Point {
int x, y;
};
template<>
struct std::formatter<Point> {
constexpr auto parse(format_parse_context& ctx) {
return ctx.begin();
}
auto format(const Point& p, format_context& ctx) const {
return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};
Point p{10, 20};
std::format("Point: {}", p); // "Point: (10, 20)"
Advanced Custom Formatting
Support format specifications:
template<>
struct std::formatter<Point> {
char presentation = 'c'; // 'c' for cartesian, 'p' for polar
constexpr auto parse(format_parse_context& ctx) {
auto it = ctx.begin();
if (it != ctx.end() && (*it == 'c' || *it == 'p')) {
presentation = *it++;
}
return it;
}
auto format(const Point& p, format_context& ctx) const {
if (presentation == 'p') {
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(), "r={:.2f}, θ={:.2f}", r, theta);
}
return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};
Point p{3, 4};
std::format("{:c}", p); // "(3, 4)"
std::format("{:p}", p); // "r=5.00, θ=0.93"
format_to: Output Iterators
Write directly to output iterators for efficiency:
std::string buffer;
std::format_to(std::back_inserter(buffer), "Value: {}", 42);
// buffer == "Value: 42"
std::vector<char> vec;
std::format_to(std::back_inserter(vec), "Hello {}", "World");
format_to_n: Bounded Output
Format with size limit:
char buffer[20];
auto result = std::format_to_n(buffer, sizeof(buffer), "Value: {}", 12345);
// result.out points past the last written character
// result.size is the total that would have been written
formatted_size: Size Calculation
Calculate size without formatting:
size_t size = std::formatted_size("Value: {}, {}", 42, "test");
std::string buffer;
buffer.reserve(size);
std::format_to(std::back_inserter(buffer), "Value: {}, {}", 42, "test");
Localization Support
std::locale loc("de_DE.UTF-8");
double value = 1234.56;
std::format("{:L}", value); // "1,234.56" (default locale)
std::format(loc, "{:L}", value); // "1.234,56" (German locale)
Escaping Braces
std::format("{{}}"); // "{}"
std::format("{{{}}} ", 42); // "{42} "
vformat: Runtime Format Strings
When format string isn’t known at compile-time:
std::string fmt = get_format_from_user(); // Runtime string
std::string result = std::vformat(fmt, std::make_format_args(arg1, arg2));
Performance Benefits
std::format is typically faster than iostream:
// Iostream: multiple virtual calls, manipulator overhead
std::ostringstream oss;
oss << "Value: " << std::setw(10) << std::setfill('0') << value;
// std::format: single operation, optimized
auto str = std::format("Value: {:010}", value);
Migration from printf
// printf
printf("%s: %d items, %.2f total\n", name.c_str(), count, total);
// std::format
std::println("{}: {} items, {:.2f} total", name, count, total);
// or
std::cout << std::format("{}: {} items, {:.2f} total\n", name, count, total);
C++23 std::print and std::println
C++23 adds convenience functions:
std::print("Hello, {}!\n", name); // Print to stdout
std::println("Value: {}", value); // Print with newline
std::print(stderr, "Error: {}\n", msg); // Print to stderr
Conceptual Check
What is the advantage of std::format over printf?
Interactive Lab
Complete the Code
double price = 19.99; int quantity = 3; // Format as: "Total: $59.97 (3 items)" std::string msg = std::format();
Runtime Environment
Interactive Lab
1#include <iostream>
2#include <format>
3#include <string>
4
5int main() {
6 std::string name = "Alice";
7 int score = 95;
8 double average = 87.456;
9
10 std::cout << std::format("Student: {}
11", name);
12 std::cout << std::format("Score: {:3d}/100
13", score);
14 std::cout << std::format("Average: {:6.2f}
15", average);
16 std::cout << std::format("Hex: 0x{:04X}
17", score);
18 std::cout << std::format("Binary: 0b{:08b}
19", score);
20 std::cout << std::format("{:=^30}
21", " Summary ");
22
23 return 0;
24}
System Console
Waiting for signal...