How fmt::format Handles Width, Alignment, and Precision Specifiers in fmtlib
fmtlib parses width, alignment, and precision in three distinct stages—fill/align, width, then precision—storing results in a fmt::format_specs object defined in include/fmt/base.h before applying them during the formatting phase.
The fmt library provides a fast, type-safe alternative to printf-style formatting in C++. According to the fmtlib/fmt source code, fmt::format processes format specifiers through a pipeline that populates a compact format_specs structure, enabling both compile-time optimization via constexpr parsing and runtime flexibility through dynamic arguments.
The format_specs Storage Structure
All parsed format specifications are stored in the fmt::format_specs structure, located in include/fmt/base.h. This class inherits from basic_specs and encapsulates three critical fields:
align: An enum indicating left (<), right (>), center (^), or sign-aware (=) alignmentwidth: An integer specifying minimum field width (defaults to0)precision: An integer controlling digit count or character limits (defaults to-1for unspecified)
Stage 1: Parsing Fill and Alignment
The parsing pipeline begins with detail::parse_align in include/fmt/format.h. This function examines the format string for an optional fill character followed by one of four alignment flags:
<for left alignment>for right alignment^for center alignment=for sign-aware alignment (places the sign before padding)
The detected alignment is converted to an align enum value and written to specs.align. If no alignment is specified, the default depends on the argument type.
Stage 2: Parsing Width
Following alignment processing, detail::parse_width (defined in include/fmt/base.h) extracts the width specifier. This function accepts either a literal decimal integer or a dynamic * character. When * is encountered, the parser consumes the next argument as the width value. The resulting integer is stored in format_specs::width, with 0 indicating no minimum width constraint.
Stage 3: Parsing Precision
When the parser encounters a dot (.), it invokes detail::parse_precision (also in include/fmt/base.h). Similar to width parsing, this accepts either a decimal integer or * for dynamic precision from arguments. The parsed value is stored in format_specs::precision, where -1 represents "unspecified" and triggers type-specific default behavior.
Applying Specifications During Formatting
During the output generation phase, the stored specifications drive padding and truncation logic:
Width handling occurs in detail::write (approximately line 2280 in include/fmt/format.h). The formatter computes the display width of the formatted value and inserts fill characters until specs.width columns are reached.
Alignment implementation distributes padding according to specs.align:
- Right alignment: padding precedes the value
- Left alignment: padding follows the value
- Center alignment: padding splits evenly on both sides
- Sign-aware alignment: places the sign character first, then pads the numeric portion
Precision enforcement varies by data type and is handled in specialized functions within include/fmt/format.h (lines 2190–2340):
- Integers (
format_int): Precision specifies minimum digit count, adding leading zeros when necessary - Floating-point (
format_float): Controls digits after the decimal point - Strings (
format_string): Limits the number of characters emitted
Working Code Examples
#include <fmt/core.h>
int main() {
// Width (right-aligned, default fill is space)
fmt::print("{:10}\n", 42); // " 42"
// Left alignment
fmt::print("{:<10}\n", 42); // "42 "
// Center alignment with custom fill character
fmt::print("{:#^10}\n", 42); // "####42####"
// Sign-aware alignment (padding after sign)
fmt::print("{:=10}\n", -42); // "- 42"
// Precision for floating-point numbers
fmt::print("{:.2f}\n", 3.14159); // "3.14"
// Precision for strings (truncate)
fmt::print("{:.5}\n", "abcdefg"); // "abcde"
// Dynamic width and precision from arguments
fmt::print("{:*>{}} {:.{}f}\n", 42, 10, 3.14159, 6, 2);
// Output: "******42 3.14"
}
Key Implementation Files
| File | Role |
|---|---|
include/fmt/base.h |
Defines format_specs (inheriting from basic_specs) and parsing helpers parse_width, parse_precision |
include/fmt/format.h |
Implements detail::parse_align and formatting logic in detail::write, format_int, format_float, and format_string |
src/format.cc |
Provides concrete instantiations of formatting functions for built-in types |
Summary
- fmtlib employs three-stage parsing: fill/align, width, then precision, all stored in
format_specs - Default values are
0for width and-1for precision (unspecified) - Dynamic specifiers use
*to pull values from subsequent arguments at runtime detail::parse_align,detail::parse_width, anddetail::parse_precisionhandle the respective parsing stages ininclude/fmt/base.handinclude/fmt/format.h- Padding logic resides in
detail::write(include/fmt/format.hline ~2280), which implements alignment rules during output generation constexprparsing enables compile-time optimization for constant format strings
Frequently Asked Questions
How does fmtlib handle dynamic width and precision specifiers?
When the format string contains * for width or precision, detail::parse_width and detail::parse_precision extract the next argument as an integer value. This dynamic value is stored directly in format_specs::width or format_specs::precision, allowing runtime control over field dimensions.
What is the difference between center alignment and sign-aware alignment?
Center alignment (^) places the value in the middle with equal padding on both sides. Sign-aware alignment (=) places the sign character first, then pads the numeric portion to reach the specified width. Sign-aware alignment only affects numeric types and requires an explicit sign in the format specifier.
What happens when precision is set to zero versus left unspecified?
An unspecified precision (default -1) allows the type-specific formatter to use natural defaults, such as the full string length or default floating-point digits. A precision of 0 explicitly truncates strings to zero characters or removes the decimal portion from floating-point numbers.
Where does the actual padding occur in the fmtlib source code?
The padding logic resides in detail::write within include/fmt/format.h (approximately line 2280). This function consults format_specs::align to determine padding placement and repeatedly writes the fill character until the output reaches format_specs::width columns.
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 →