How fmtlib Implements the POSIX Positional printf Extension

fmtlib implements the POSIX positional printf extension in include/fmt/printf.h through a coordinated parsing system where parse_header extracts explicit argument indices and a get_arg lambda resolves them against the argument list, enabling both reordered arguments and positional width/precision specifiers.

The {fmt} library (fmtlib/fmt) provides a modern C++ formatting API that includes comprehensive support for POSIX positional printf syntax. This extension allows format strings to reference arguments by explicit position (e.g., %2$d %1$s) and to specify widths or precisions via separate argument indices (e.g., %*3$d). The implementation centers on two tightly-coupled components within include/fmt/printf.h that handle parsing and argument resolution.

Core Parsing Components

The implementation relies on two primary mechanisms defined in include/fmt/printf.h:

  • parse_header (lines 330-381): Parses optional argument indices (%2$d), width specifications (%*3$d), and precision modifiers (%.*4$f). It detects the trailing $ character that denotes a positional index and returns the resolved argument index to the formatter.

  • get_arg lambda (lines 13-23): Retrieves arguments either by the next sequential index or by an explicit index supplied by parse_header. It validates indices against the parsing context to prevent out-of-bounds access.

Parsing Argument Indices

When the parser encounters a digit (0-9) immediately following a % character, it invokes parse_nonnegative_int. If the next character after the number is $, the numeric value is interpreted as an explicit argument index rather than a width or precision value.

if (it != end && *it == '$') {          // value is an argument index
    ++it;
    arg_index = value != -1 ? value : max_value<int>();
}

The computed arg_index is passed to the get_arg lambda, which adjusts the value by decreasing it by one to account for zero-based internal identifiers. This enables syntax like %2$d to correctly reference the second argument in the parameter list.

Positional Width and Precision

The POSIX extension allows * width and .* precision specifiers to reference specific arguments using the *N$ and .*N$ syntax. Inside parse_header, after handling standard width parsing, the code checks for the * character followed by a numeric index:

else if (*it == '*') {
    ++it;
    // Check for positional width argument like *1$
    if (it != end && *it >= '0' && *it <= '9') {
        int width_index = parse_nonnegative_int(it, end, -1);
        if (it != end && *it == '$') {
            ++it;
            specs.width = static_cast<int>(
                get_arg(width_index).visit(detail::printf_width_handler(specs)));
        } else {
            report_error("invalid format specifier");
        }
    } else {
        specs.width = static_cast<int>(
            get_arg(-1).visit(detail::printf_width_handler(specs)));
    }
}

The same pattern appears for precision handling at lines 458-471 in the same file. The get_arg(width_index) call retrieves the N-th argument, which printf_width_handler or printf_precision_handler then processes to obtain the concrete integer value.

Argument Retrieval Logic

The vprintf function creates a parse_context that tracks the total argument count and the "next" argument position. The get_arg lambda implements the resolution logic:

auto get_arg = [&](int arg_index) {
    if (arg_index < 0)
        arg_index = parse_ctx.next_arg_id();          // sequential
    else
        parse_ctx.check_arg_id(--arg_index);          // explicit positional
    auto arg = context.arg(arg_index);
    if (!arg) report_error("argument not found");
    return arg;
};

When arg_index is negative, the system uses sequential argument ordering. For explicit positive indices, the lambda validates the index and decrements it to convert from the user-facing one-based indexing to the internal zero-based representation used by basic_printf_context.

Integration with the printf Engine

After parse_header returns the resolved arg_index, the formatter executes a four-stage pipeline:

  1. Precision parsing: Identical handling of *N$ syntax for precision specifiers.
  2. Argument conversion: The selected argument is transformed to the required type via convert_arg.
  3. Presentation type resolution: parse_printf_presentation_type determines the final conversion (decimal, hexadecimal, floating-point, etc.).
  4. Formatting: The argument is rendered through printf_arg_formatter.

This pipeline resides within detail::vprintf, which serves as the implementation core for public APIs including fmt::printf, fmt::sprintf, and related functions.

Practical Code Examples

The following examples demonstrate POSIX positional syntax in fmtlib:

// Positional arguments: %2$d prints the second argument, %1$s prints the first.
fmt::printf("%2$d %1$s\n", "hello", 42);
// Output: 42 hello

// Positional width: *3$ takes the width from the third argument.
fmt::printf("%*3$d\n", 0, 0, 5, 123);
// Output:   123 (5-character wide field)

// Positional precision: .*4$f takes the precision from the fourth argument.
fmt::printf("%.*4$f\n", 0, 0, 0, 3, 2.71828);
// Output: 2.718

These patterns allow format strings to reference arguments in any order and to source dynamic widths or precisions from specific parameter positions.

Summary

  • Primary implementation resides in include/fmt/printf.h, specifically within the parse_header function (lines 330-381) and the get_arg lambda (lines 13-23).
  • Argument indexing uses one-based numbering in format strings (e.g., %2$d) that converts to zero-based internal indices during retrieval.
  • Positional specifiers support both direct value substitution and indirect width/precision references using the *N$ and .*N$ syntax.
  • Validation occurs through parse_ctx.check_arg_id(), ensuring explicit indices remain within bounds before argument retrieval.
  • Fallback behavior defaults to sequential argument processing when no explicit index is provided (indicated by negative index values).

Frequently Asked Questions

What is the POSIX positional printf extension?

The POSIX positional printf extension is a formatting syntax enhancement that allows format strings to specify exactly which argument to use for a particular conversion specifier by placing a decimal index and $ immediately after the % (e.g., %2$d). It also permits width and precision to be taken from specific arguments using *N$ and .*N$ syntax, enabling argument reordering and dynamic formatting control.

How does fmtlib handle mixed positional and non-positional arguments?

According to the source code in include/fmt/printf.h, fmtlib requires format strings to use either all positional or all non-positional arguments; mixing styles within a single format string results in an error. The get_arg lambda distinguishes between modes by checking whether arg_index is negative (sequential) or positive (explicit), with parse_ctx tracking the next sequential position only when no explicit indices are present.

Can I use positional arguments for width and precision in fmtlib?

Yes, fmtlib fully supports positional width and precision specifiers. You can write %*3$d to use the third argument as the field width, or %.*4$f to use the fourth argument as the precision for a floating-point conversion. The parse_header function at lines 330-381 handles these cases by detecting the $ character after the asterisk and numeric index.

Where is the positional argument parsing logic located in the fmt source code?

The core parsing logic resides in include/fmt/printf.h within the parse_header function (lines 330-381) for extracting indices and the get_arg lambda (lines 13-23) for resolving them. Additional supporting infrastructure appears in include/fmt/format.h, which defines format_specs and parse_context that the printf implementation reuses for argument management and validation.

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 →