fmtlib Format String Syntax: The Complete Guide to Modern C++ Formatting
fmtlib uses a Python-style curly-brace mini-language where replacement fields {arg_id:format_spec} control how values are rendered, supporting positional arguments, named arguments, width, precision, alignment, and type-specific formatting.
The {fmt} library—commonly known as fmtlib—provides a fast, type-safe alternative to printf and iostreams for C++. Its format string syntax combines the readability of Python's str.format() with compile-time validation and extensibility. Every format string in fmt::format, fmt::print, and related functions follows a well-defined grammar implemented across the include/fmt/ headers.
Anatomy of a Replacement Field
A replacement field is the core unit of fmtlib's format string syntax. Fields are delimited by curly braces and follow this grammar:
{ [arg_id] [ : (format_spec | chrono_format_spec) ] }
Text outside replacement fields is copied verbatim. To output literal braces, double them: {{ produces { and }} produces }.
Argument Selection with arg_id
The arg_id component selects which argument to format:
| Style | Example | Behavior |
|---|---|---|
| Automatic | "{}" |
Consumes arguments left-to-right |
| Positional | "{1}" |
Selects argument at index 1 (0-based) |
| Named | "{name}" |
Matches fmt::arg("name", value) |
Mixing automatic and explicit positional indices is prohibited—the parser enforces this at compile time when possible.
Format Specification Syntax
The format_spec follows the colon in a replacement field and controls presentation details. The full grammar is documented at [doc/syntax.md](https://github.com/fmtlib/fmt/blob/main/doc/syntax.md#format-spec).
Fill and Alignment
Controls padding character and placement:
fmt::format("[{:*^10}]", "42"); // => "[****42****]"
fmt::format("[{:<10}]", "left"); // => "[left ]"
fmt::format("[{:>10}]", "right"); // => "[ right]"
| Specifier | Meaning |
|---|---|
< |
Left-align |
> |
Right-align |
^ |
Center-align |
Any character before <>^ |
Fill character (default: space) |
Sign, Alternate Form, and Zero Padding
fmt::format("{:+d}", 42); // => "+42" (always show sign)
fmt::format("{: d}", 42); // => " 42" (space for positive)
fmt::format("{:08d}", 42); // => "00000042" (zero-padding)
fmt::format("{:#x}", 255); // => "0xff" (alternate form)
fmt::format("{:+#010x}", 255); // => "+0x0000ff" (combined)
| Flag | Effect |
|---|---|
+ |
Always show sign for signed numbers |
- |
Show minus only (default) |
(space) |
Leading space for positive numbers |
# |
Alternate form (prefix for base, decimal point for floats) |
0 |
Zero-pad to width (ignores fill/align) |
Width and Precision
Both can be static values or dynamic values via nested replacement fields:
// Static width
fmt::format("[{:10}]", "hi"); // => "[hi ]"
// Dynamic width from argument
fmt::format("[{:{} }]", "hi", 10); // => "[hi ]"
// Static precision
fmt::format("{:.2f}", 3.14159); // => "3.14"
// Dynamic precision
fmt::format("{:.{}f}", 3.14159, 1); // => "3.1"
// Width and precision from arguments
fmt::format("{:{}.{}f}", 3.14159, 8, 2); // => "[ 3.14]"
Locale-Aware Formatting with L
The L flag enables locale-specific formatting such as thousands separators:
auto loc = std::locale("en_US.UTF-8");
fmt::format(loc, "{:L}", 1234567); // => "1,234,567"
fmt::format(loc, "{:L}", 1234.5); // => "1,234.5"
Type Specifiers
The final character (or characters) in format_spec determines the presentation type:
| Type | Applies To | Output Style |
|---|---|---|
d, i |
Integer | Decimal |
b |
Integer | Binary (with # → 0b prefix) |
B |
Integer | Binary uppercase (0B) |
o |
Integer | Octal |
x |
Integer | Hexadecimal |
X |
Integer | Hexadecimal uppercase |
f, F |
Floating | Fixed-point (F for uppercase INF/NAN) |
e, E |
Floating | Scientific notation |
g, G |
Floating | General (shorter of f/e) |
a, A |
Floating | Hexadecimal float |
s |
String/string-like | Plain string |
? |
String | Debug/quoted string |
p |
Pointer | 0x prefix address |
c |
Integer/char | Character |
? |
Any | Debug representation |
fmt::format("{:b}", 255); // => "11111111"
fmt::format("{:#b}", 255); // => "0b11111111"
fmt::format("{:X}", 255); // => "FF"
fmt::format("{:e}", 1234.5); // => "1.234500e+03"
fmt::format("{:p}", nullptr); // => "0x0"
Chrono Format Specification
For std::chrono durations, time points, and std::tm, fmtlib extends the basic spec with chrono_format_spec. This uses standard strftime-style conversion specifiers:
auto t = std::tm{};
t.tm_year = 2023 - 1900;
t.tm_mon = 3; // April (0-based)
t.tm_mday = 5;
fmt::format("{:%Y-%m-%d %H:%M:%S}", t); // => "2023-04-05 00:00:00"
fmt::format("{:%B %d, %Y}", t); // => "April 05, 2023"
// With chrono durations
using namespace std::chrono;
fmt::format("{:%H:%M:%S}", 3661s); // => "01:01:01"
The chrono grammar supports width, precision, and locale modifiers alongside time conversion characters. See doc/syntax.md#chrono-format-spec for the complete reference.
Practical Code Examples
Basic Positional and Named Arguments
#include <fmt/format.h>
// Automatic indexing
fmt::print("Hello, {}!\n", "world");
// Explicit positional (0-based, reorderable)
fmt::print("{1} {0} {2}\n", "a", "b", "c"); // => "b a c"
// Named arguments with fmt::arg
fmt::print("{greeting}, {name}!\n",
fmt::arg("greeting", "Good morning"),
fmt::arg("name", "Developer"));
Advanced Format Specifications
#include <fmt/format.h>
#include <fmt/chrono.h>
#include <iostream>
#include <chrono>
// Table-like formatting with alignment
fmt::print("{:<10} {:>6} {:>8}\n", "Item", "Qty", "Price");
fmt::print("{:<10} {:>6} {:>8.2f}\n", "Apples", 12, 3.5);
fmt::print("{:<10} {:>6} {:>8.2f}\n", "Oranges", 6, 2.25);
// Binary/hex debugging output
for (uint8_t b : {0x41, 0x42, 0x43}) {
fmt::print("0b{:08b} 0x{:02X} '{}'\n", b, b, b);
}
// Chrono with custom formatting
auto now = std::chrono::system_clock::now();
fmt::print("ISO: {:%Y-%m-%dT%H:%M:%S%z}\n", now);
Compile-Time Validation
When format strings are string literals, errors are caught at compile time:
// This compiles: type matched
fmt::format("Value: {}", 42);
// This fails at compile time: type mismatch
// fmt::format("Value: {:f}", "not a float"); // ERROR
// This fails at compile time: index out of range
// fmt::format("{2}", "a", "b"); // ERROR: argument index out of range
Runtime format strings (via fmt::runtime) defer validation:
std::string user_fmt = "{:.{}f}";
fmt::format(fmt::runtime(user_fmt), 3.14, 1); // Runtime parsing
Implementation and Source Files
The format string syntax is parsed and interpreted across these key source locations:
include/fmt/format.h— Declaresfmt::format,fmt::print, and the coreformat_stringtype that drives compile-time validationinclude/fmt/chrono.h— Implements chrono_format_spec parsing for date/time typesinclude/fmt/printf.h— Providesfmt::printffor legacy printf-style format stringsdoc/syntax.md— Definitive grammar documentation for all specification components
The compile-time parsing leverages C++20 consteval (or constexpr techniques in C++17) to validate format strings against argument types before program execution.
Summary
- Structure: fmtlib format strings contain literal text and replacement fields
{arg_id:format_spec}, with doubled braces for literal braces - Arguments: Support automatic positional, explicit positional, and named arguments via
fmt::arg - Specifications: After the colon, control fill/align, sign,
#alternate form,0padding, width, precision,Llocale, and presentation type - Chrono: Special format spec for time types using
%-based conversion characters - Safety: String literal format strings are validated at compile time; runtime strings use
fmt::runtime
Frequently Asked Questions
What is the difference between fmt::format and std::format?
fmt::format is the original implementation in fmtlib that became the basis for C++20's std::format. The fmtlib version offers broader compiler support (C++11 and later), additional features like dynamic width/ precision with runtime values, and faster release cycles. As of C++20, std::format provides the same core syntax with standard library integration.
How do I escape curly braces in fmtlib format strings?
Double the braces: {{ produces a literal { and }} produces a literal }. This is necessary when you need brace characters in output, such as generating JSON or C++ code templates.
Can I mix automatic and manual argument indexing?
No—fmtlib prohibits mixing {} (automatic) with {0}, {1} (explicit positional) in the same format string. Choose one style per call. Named arguments can coexist with automatic indexing but not with explicit positional indices.
What happens if my format string has an error?
For string literal format strings, errors trigger compile-time failures with descriptive messages. For runtime format strings passed via fmt::runtime, errors throw fmt::format_error at execution time.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →