C++20/23 Format Library: Modern String Formatting in C++
Master C++20/23 format library with practical examples.
20+ years shipping performance-critical C and C++ systems. Drawn from code that ran under real load.
- ✓Basic knowledge of C++ syntax and standard library.
- ✓Familiarity with templates and function overloading.
- ✓Understanding of strings and I/O.
- std::format provides type-safe, Python-like string formatting.
- Supports positional and named arguments, width, precision, fill/align.
- Custom formatters for user-defined types.
- Faster and safer than printf and iostreams.
- Available in C++20, with enhancements in C++23.
Think of std::format like a fill-in-the-blank template. You write a string with placeholders like {} and provide values to insert. It automatically handles types and formatting, like setting a table with labeled slots for dishes.
String formatting is a fundamental task in programming, used for logging, user interfaces, data serialization, and debugging. For decades, C++ developers relied on printf (inherited from C) or iostreams (std::cout, std::stringstream). Both have drawbacks: printf is type-unsafe and error-prone; iostreams are verbose and slow. The C++20 standard introduced the format library (std::format), inspired by Python's str.format and the {fmt} library. It combines the best of both worlds: type safety, performance, and expressive syntax. C++23 further improves it with std::print and std::println. This tutorial covers everything you need to use the format library effectively in production code, from basic usage to custom formatters and debugging.
Basic Usage of std::format
The std::format function takes a format string and a variable number of arguments. Placeholders are denoted by curly braces {}. The simplest form is {} which outputs the argument in its default format. Arguments are indexed automatically starting from 0. You can also use explicit indices like {0}, {1} to reorder arguments. Named arguments are not supported directly, but you can use std::format with a map or tuple.
Example: std::format("Hello, {}!", "world") returns "Hello, world!".
Format specifiers follow a colon after the index: {0:spec}. Common specifiers include d for decimal, x for hex, f for fixed-point, e for scientific, and s for string. Width and precision are specified as {0:10} (minimum width 10) or {0:.2f} (2 decimal places). Alignment is done with <, >, ^ for left, right, center.
Format Specifiers in Depth
Format specifiers follow the pattern: [[fill]align][sign][#][0][width][.precision][type]. Fill character defaults to space. Align: < (left), > (right), ^ (center). Sign: + (always show sign), - (only negative), space (space for positive). # adds alternate form (e.g., 0x for hex). 0 pads with zeros. Width is minimum field width. Precision for floating-point is number of digits after decimal; for strings, maximum length. Type: d, i, u, o, x, X, f, F, e, E, g, G, a, A, s, c, p.
- {:>10} right-align in width 10
- {:<+10.2f} left-align, sign, width 10, 2 decimals
- {:#x} hex with 0x prefix
- {:010} zero-padded width 10
- {:.5s} string truncated to 5 chars
Formatting User-Defined Types
To format custom types, specialize std::formatter for your type. The specialization must provide two member functions: parse() and format(). parse() reads the format specifier from the format string (between { and :). format() writes the formatted output to the output iterator. The parse method should return an iterator past the parsed specifier. The format method uses the specifier to format the value.
Example: Format a Point struct as (x, y). Support precision for coordinates.
Performance and std::format_to
std::format returns a std::string, which involves allocation. For performance-critical code, use std::format_to which writes to an output iterator. You can write to a pre-allocated buffer, a file, or a custom sink. std::format_to returns an iterator past the last written character. Also consider using std::formatted_size to compute the required buffer size without formatting.
Example: Write formatted output to a fixed-size buffer.
Locale and Unicode Support
std::format respects the global locale for formatting numbers (e.g., thousands separator). Use the 'L' specifier to enable locale-specific formatting. For example, {:L} formats an integer with locale-specific digit grouping. Unicode support is limited; std::format works with char and wchar_t strings. For UTF-8, use std::string (char8_t in C++20). However, width and alignment may not correctly handle multi-byte characters. Consider using libraries like fmt for full Unicode support.
Example: Locale-aware formatting.
Error Handling and Debugging
std::format throws std::format_error on invalid format strings (e.g., mismatched braces, invalid specifiers). Always catch exceptions in production code. Use try-catch blocks around format calls. For debugging, you can use std::vformat with a std::format_args to inspect arguments. Also, consider using compile-time checks with constexpr format strings (C++20 allows constexpr std::format).
Example: Catching format errors.
The Logging Outage: How printf Crashed a Production Server
- Always use type-safe formatting functions.
- Avoid printf in modern C++ code.
- Enable compiler warnings for format string mismatches.
- Use static analysis tools to detect unsafe calls.
- Prefer std::format for new code.
std::format("{{hello}}")std::format("{{}}", 42)| File | Command / Code | Purpose |
|---|---|---|
| basic_format.cpp | int main() { | Basic Usage of std |
| format_specifiers.cpp | int main() { | Format Specifiers in Depth |
| custom_formatter.cpp | struct Point { | Formatting User-Defined Types |
| format_to.cpp | int main() { | Performance and std |
| locale_format.cpp | int main() { | Locale and Unicode Support |
| error_handling.cpp | int main() { | Error Handling and Debugging |
Key takeaways
Common mistakes to avoid
3 patternsForgetting to escape braces: using { instead of {{ for literal brace.
Mismatching argument types: e.g., using {:d} with a string.
Assuming std::format modifies the arguments.
Interview Questions on This Topic
Explain the syntax of format specifiers in std::format.
Frequently Asked Questions
20+ years shipping performance-critical C and C++ systems. Drawn from code that ran under real load.
That's C++ Advanced. Mark it forged?
3 min read · try the examples if you haven't