fmtlib Format String Syntax: The Complete Guide to Python-Style Formatting in C++
fmtlib uses a Python-inspired format string syntax where replacement fields enclosed in curly braces ({}) support optional argument IDs, conversion flags, and detailed format specifications including alignment, fill characters, width, precision, and type specifiers.
The {fmt} library (repository fmtlib/fmt) provides a fast, type-safe alternative to printf and C++ streams. Its format string syntax is formally defined in doc/syntax.md and implemented primarily in include/fmt/format.h and include/fmt/format-inl.h, offering both compile-time validation and runtime flexibility.
Core Structure of Format Strings
A format string consists of literal text interleaved with replacement fields delimited by { and }. Everything outside braces is copied verbatim to the output, while content inside braces is interpreted according to the replacement field grammar.
Replacement Field Grammar
Each replacement field follows this general structure:
{[arg_id][!conversion][:format_spec]}
arg_id: An optional positional index (e.g.,0,1) or named argument identifier (e.g.,name). If omitted, arguments are consumed in order.!conversion: Currently reserved for future extensions in thefmtlibcodebase.format_spec: A colon-prefixed string defining presentation rules (alignment, width, precision, type, etc.).
Format Specification Layout
When a colon is present, the format_spec components must appear in this strict order:
[fill][align][sign][#][0][width][.precision][L][type]
fill: Any character used to pad the field (default is space).align:<(left),>(right),^(center), or=(pad after sign for numeric types).sign:+(always show sign),-(only negative), or space (leading space for positives).#: Enables alternate form (e.g.,0xprefix for hex).0: Zero-padding for numeric types (equivalent tofill=0withalign='=').width: Minimum field width as integer or*to consume an additional argument.precision: Dot (.) followed by number or*; controls digits after decimal for floats or max length for strings.L: Locale-specific formatting (requires locale support).type: Presentation type (e.g.,d,x,f,s,p,c).
Argument Selection Methods
Positional arguments can be referenced explicitly by zero-based index or consumed automatically.
#include <fmt/core.h>
fmt::print("{1} {0}\n", "world", "Hello"); // Output: Hello world
fmt::print("{} {}\n", "Hello", "world"); // Output: Hello world
Named arguments allow passing a string key for clarity, particularly useful when formatting complex objects.
fmt::print("{greeting}, {name}!\n",
fmt::arg("greeting", "Hello"),
fmt::arg("name", "world"));
Detailed Specifier Reference
Alignment and Fill
Combine any fill character with an alignment option to control padding.
fmt::print("|{:*<10}|\n", "left"); // |left******|
fmt::print("|{:=>10}|\n", -42); // |======-42|
fmt::print("|{:-^10}|\n", "mid"); // |---mid----|
Numeric Formatting Options
Control sign presentation, alternate forms, and zero-padding for arithmetic types.
fmt::print("{:+d}\n", 42); // +42
fmt::print("{: d}\n", 42); // 42 (leading space)
fmt::print("{:#x}\n", 255); // 0xff (alternate form)
fmt::print("{:08d}\n", 42); // 00000042 (zero-pad to width 8)
fmt::print("{:+08d}\n", 42); // +0000042
Precision and Type Specifiers
Precision affects floating-point digits or string length depending on the type.
fmt::print("{:.2f}\n", 3.14159); // 3.14 (fixed precision)
fmt::print("{:.4s}\n", "C++ Format"); // C++ (string truncation)
fmt::print("{:e}\n", 1234.5); // 1.234500e+03 (scientific)
fmt::print("{:g}\n", 1234.5); // 1234.5 (general/auto)
Locale-Aware Formatting
The L flag enables locale-specific separators for thousands and decimal points.
fmt::print("{:L}\n", 1234567.89); // 1,234,567.89 (en_US locale dependent)
Compile-Time vs Runtime Validation
fmtlib distinguishes between static and dynamic format strings to maximize safety and performance.
Compile-time validation occurs when using FMT_STRING or fmt::format_string types, causing the parser in include/fmt/format-inl.h to check syntax at compile time and emit errors for malformed specifications.
Runtime parsing is used when passing fmt::runtime_format_string (or fmt::runtime in newer versions), deferring validation to execution. This allows dynamic string construction while reusing the same parsing logic defined in the core headers.
// Compile-time checked
auto s = fmt::format(FMT_STRING("{} {}"), 42, "answer");
// Runtime checked (for dynamic strings)
std::string dyn = "{}";
auto t = fmt::format(fmt::runtime(dyn), 42);
Extending with Custom Formatters
User-defined types integrate with the syntax by specializing fmt::formatter<T> in the global namespace or within namespace fmt, as documented in doc/api.md.
The specialization must provide:
parse(format_parse_context& ctx): Parses the format spec (the portion after:) and returns an iterator past the end of the specification.format(const T& value, FormatContext& ctx): Writes the formatted output usingfmt::format_to.
#include <fmt/core.h>
struct Point {
int x, y;
};
template <>
struct fmt::formatter<Point> {
constexpr auto parse(format_parse_context& ctx) {
return ctx.begin(); // Accept any/empty spec
}
template <typename FormatContext>
auto format(const Point& p, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};
int main() {
Point p{3, 4};
fmt::print("Point: {}\n", p); // Output: Point: (3, 4)
}
Complete Syntax Examples
The following demonstrates the full range of fmtlib capabilities, including chrono formatting which uses strftime-like specifiers defined in the supplementary headers.
#include <fmt/core.h>
#include <fmt/chrono.h>
#include <iostream>
int main() {
// Positional and indexed arguments
fmt::print("Hello, {}!\n", "world");
fmt::print("{1} comes before {0}\n", "second", "first");
// Alignment, width, and fill
fmt::print("|{:*^12}|\n", "centered");
fmt::print("|{:<10}|{:>10}|\n", "left", "right");
// Integer formatting
fmt::print("Hex: {:#x}, Octal: {:#o}, Binary: {:#b}\n", 42, 42, 42);
fmt::print("Zero-padded: {:08d}\n", 123);
fmt::print("Always signed: {:+d}\n", 42);
// Floating-point precision
fmt::print("Pi = {:.2f}\n", 3.14159);
fmt::print("Scientific: {:.2e}\n", 1234.5);
// Locale-aware (requires specific locale setup)
// fmt::print("{:L}\n", 1234567);
// Chrono formatting
auto now = std::chrono::system_clock::now();
fmt::print("Time: {:%Y-%m-%d %H:%M:%S}\n", now);
}
Summary
- Replacement fields use
{[arg][:spec]}syntax defined indoc/syntax.md. - Format specifications follow the strict order:
[fill][align][sign][#][0][width][.precision][L][type]. - Argument selection supports automatic positioning, explicit indices (
{0},{1}), and named arguments. - Compile-time safety is enforced via
FMT_STRINGandfmt::format_stringparsing ininclude/fmt/format-inl.h. - Extensibility is achieved by specializing
fmt::formatter<T>withparse()andformat()methods, as reference indoc/api.md.
Frequently Asked Questions
What is the difference between positional and named arguments in fmtlib?
Positional arguments use numeric indices like {0} or {1} to reference specific parameters by position, while named arguments use string keys like {name} passed via fmt::arg("name", value). Positional arguments are more efficient for simple formatting, whereas named arguments improve readability when formatting complex objects with many fields.
How does fmtlib achieve compile-time format string validation?
When a string literal is wrapped with FMT_STRING or passed as a fmt::format_string type, the parser in include/fmt/format-inl.h evaluates the syntax during compilation. This catches mismatched braces, invalid type specifiers, and argument count mismatches before the program runs, converting them into compiler errors rather than runtime exceptions.
Can I customize the format string syntax for my own types in fmtlib?
Yes, by specializing the fmt::formatter<T> template for your type, as documented in doc/api.md. You implement a parse method to handle custom format specifications (after the colon) and a format method to write the output. This allows your types to use the same {...} syntax with custom logic as built-in types.
What is the order of specifiers in a fmtlib format specification?
The specifiers must appear exactly in this sequence: fill character (if any), alignment (<, >, ^, =), sign (+, -, space), alternate form flag (#), zero-padding flag (0), width (number or *), precision (. followed by number or *), locale flag (L), and finally the type character (e.g., d, x, f). Missing components are simply skipped, but present components must not deviate from this order.
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 →