How fmtlib's printf API Supports POSIX Positional Arguments

The fmt library implements POSIX positional arguments in its printf compatibility layer through the %n$ syntax, enabling out-of-order argument selection and dynamic width/precision specification via the parse_header parser in include/fmt/printf.h.

The fmtlib/fmt repository provides a modern C++ formatting library that includes a printf-compatible API for legacy code migration. This implementation fully supports POSIX positional arguments, allowing format strings to reference arguments by index rather than strict sequential order. The feature is implemented through specialized parsing logic that interprets the $ directive within format specifiers.

POSIX Positional Argument Syntax

The fmtlib printf API recognizes the POSIX standard %n$ syntax, where n specifies the argument index (1-based) to use for that conversion specification.

Direct Argument Indexing

To select arguments out of order, use the format %n$ followed by the conversion specifier. For example, %2$d selects the second argument for integer formatting. In include/fmt/printf.h, the parse_header function (lines 313-342) reads the digit sequence and checks for the $ character. When found, it treats the value as an argument index (arg_index) rather than a width or flag.

The retrieved index is passed to the get_arg lambda inside vprintf (lines 13-24), which fetches the corresponding argument from the argument list. A negative value passed to get_arg indicates "next argument" mode, while non-negative values force the specific index (adjusted for zero-based internal counting).

Positional Width and Precision

POSIX positional arguments extend to dynamic widths and precisions:

  • Positional width: %*2$d uses the second argument as the field width. When parse_header encounters * followed by digits and $ (lines 59-73), it retrieves the specified argument and passes it to printf_width_handler.
  • Positional precision: %.*3$f uses the third argument as precision. After parsing the dot, if * appears with a digit-$ sequence (lines 54-71), the parser calls printf_precision_handler with the chosen argument.

Implementation Details in include/fmt/printf.h

The core logic resides in the parse_header function, which serves as the entry point for parsing printf-style format specifiers.

Argument Index Parsing

When processing a format string, parse_header first accumulates digits. If the next character is $, the accumulated value becomes arg_index. This index directs the subsequent get_arg call to fetch the correct argument from the basic_format_args object. The implementation ensures that once a positional argument is used, the parser maintains index consistency throughout the rest of the format string.

The get_arg Resolution Mechanism

Inside the vprintf function, the get_arg lambda (lines 13-24) handles argument retrieval. It receives the parsed index and determines whether to access arguments sequentially or by absolute position. This mechanism supports mixing positional and non-positional specifiers, though POSIX compliance typically requires choosing one style per format string.

Type Conversion via convert_arg

After argument selection, convert_arg (referenced around lines 65-73) adapts the value to match any length modifiers (h, l, ll, etc.). This conversion step operates identically for both positional and non-positional arguments because get_arg has already resolved the specific value from the argument pack.

Practical Code Examples

#include <fmt/printf.h>

// Simple positional argument - print the second argument first
fmt::printf("%2$s %1$s!\n", "world", "Hello");  // Output: Hello world!

// Positional width - the width is taken from the second argument (10)
int width = 10;
fmt::printf("%*2$s %1$s\n", "right", width);  // Creates:       right

// Positional precision - the precision comes from the fourth argument (3)
int prec = 3;
double value = 1.23456;
fmt::printf("%.*4$f %1$s\n", "unused", value, "unused", prec);  // Output: 1.235

In the first example, %2$s selects the string "Hello" (argument 2) before %1$s selects "world". The second example demonstrates %*2$s, where the asterisk requests a width argument and 2$ specifies which argument provides it. The third example shows %.*4$f, where the precision is drawn from the fourth argument.

Summary

  • ** fmtlib's printf API** implements POSIX positional arguments through the %n$ syntax in include/fmt/printf.h.
  • The parse_header function detects positional modifiers by scanning for digit sequences followed by $, handling both direct argument references and width/precision specifiers.
  • Argument resolution occurs through the get_arg lambda in vprintf, which supports negative values for sequential access and non-negative values for indexed access.
  • Type conversion is handled uniformly by convert_arg after argument selection, supporting standard length modifiers regardless of how the argument was indexed.
  • The implementation allows mixing positional width (*n$) and precision (.*n$) specifiers with direct argument indexing.

Frequently Asked Questions

Can I mix positional and non-positional arguments in the same format string?

While the fmtlib implementation technically supports mixing styles through the get_arg lambda's logic, POSIX compliance requires using either all positional or all non-positional arguments in a single format string. Mixing styles may lead to undefined behavior or compilation errors depending on the specific format checkers enabled.

Does using positional arguments impact runtime performance?

According to the source code in include/fmt/printf.h, positional arguments introduce minimal overhead compared to standard printf formatting. The parse_header function performs the index calculation during parsing, and get_arg uses simple array indexing to retrieve arguments. The performance characteristics are comparable to standard argument traversal.

Which fmtlib functions support POSIX positional arguments?

All printf-compatible functions in the fmt library support this syntax, including fmt::printf, fmt::sprintf, fmt::fprintf, and their wide-character variants. The functionality is centralized in the vprintf implementation within include/fmt/printf.h, ensuring consistent behavior across the entire printf API surface.

How does fmtlib handle invalid positional indices?

If a format string specifies an argument index that exceeds the provided argument count, the behavior follows the library's standard error handling mechanisms. The get_arg function performs bounds checking against the basic_format_args object, typically throwing a format_error exception when an out-of-range index is requested.

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 →