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 (=) alignment
  • width: An integer specifying minimum field width (defaults to 0)
  • precision: An integer controlling digit count or character limits (defaults to -1 for 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 0 for width and -1 for precision (unspecified)
  • Dynamic specifiers use * to pull values from subsequent arguments at runtime
  • detail::parse_align, detail::parse_width, and detail::parse_precision handle the respective parsing stages in include/fmt/base.h and include/fmt/format.h
  • Padding logic resides in detail::write (include/fmt/format.h line ~2280), which implements alignment rules during output generation
  • constexpr parsing 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →