How `parse_dynamic_spec` Handles Literal and Argument References for Width/Precision in fmtlib
parse_dynamic_spec distinguishes between literal integer values and dynamic argument references (implicit, explicit positional, or named) to configure width and precision at runtime in the fmt library.
The fmt library (fmtlib/fmt) provides a high-performance formatting system for C++. When parsing format specifiers like {:{}} or {:10}, the library relies on parse_dynamic_spec to determine whether width or precision values are supplied as compile-time literals or extracted from runtime arguments. This function, defined in include/fmt/core.h, serves as the critical bridge between static format strings and dynamic formatting parameters.
How parse_dynamic_spec Parses Literal Integers
When parse_dynamic_spec encounters a digit, it treats the value as a literal width or precision. The function checks if the first character falls within the 0 to 9 range and delegates to parse_nonnegative_int to extract the integer value.
If the input character is a digit, the parser reads the integer directly:
if ('0' <= *begin && *begin <= '9') {
int val = parse_nonnegative_int(begin, end, -1);
if (val == -1) report_error("number is too big");
value = val; // literal width/precision
}
In this branch, the function stores the parsed integer in the value reference parameter and leaves the returned kind as arg_id_kind::none (indicating no argument lookup is required). This logic appears in include/fmt/core.h at approximately lines 1398-1406.
Resolving Dynamic Argument References
When the specifier begins with a {, parse_dynamic_spec enters dynamic resolution mode, supporting three distinct reference types.
Implicit Positional Arguments ({} or {:})
If the character following the opening brace is } or :, the parser recognizes an implicit reference to the next argument in the format string. The implementation calls ctx.next_arg_id() to retrieve the current positional index, records it in the ref parameter, and sets kind to arg_id_kind::index.
if (c == '}' || c == ':') {
int id = ctx.next_arg_id();
ref = id;
kind = arg_id_kind::index;
ctx.check_dynamic_spec(id);
}
This mechanism allows formats like fmt::format("{:{}}", value, width) where the second argument supplies the width dynamically. The check_dynamic_spec call validates that the referenced argument exists and is suitable for width or precision specification (source: include/fmt/core.h, lines 1410-1416).
Explicit Positional and Named References ({N} or {name})
For explicit references (e.g., {2} or {width}), the parser delegates to parse_arg_id with a dynamic_spec_handler callback. This handler distinguishes between numeric indices and named arguments:
- Positional index: Sets
ref = idandkind = arg_id_kind::index - Named argument: Sets
ref = idandkind = arg_id_kind::name
Both cases invoke ctx.check_dynamic_spec(id) to validate the reference:
begin = parse_arg_id(begin, end,
dynamic_spec_handler<Char>{ctx, ref, kind});
This branch handles advanced syntax like {: {2}.{1}} where specific argument positions define width and precision, or named references such as {:{width}.{prec}} (source: include/fmt/core.h, lines 1417-1419).
Error Handling for Invalid Specifiers
When parse_dynamic_spec encounters malformed braces or unsupported tokens after {, it immediately reports an error:
report_error("invalid format string");
This safety check appears at lines 1422-1425 in include/fmt/core.h, ensuring that incomplete dynamic specifiers like {: or {invalid trigger clear compile-time or runtime errors rather than silent failures.
Integration with Width and Precision Parsing
The parse_dynamic_spec function does not operate in isolation. Higher-level functions parse_width and parse_precision (also in include/fmt/core.h) invoke it to populate format_specs objects:
specs.set_dynamic_width(result.kind);
// or
specs.set_dynamic_precision(result.kind);
Later, during the actual formatting phase, detail::handle_dynamic_spec (defined in include/fmt/format.h around lines 4040-4052) retrieves the argument value using the recorded kind and ref (index or name) to substitute the final width or precision at runtime.
Practical Code Examples
Here are concrete examples demonstrating how parse_dynamic_spec interprets different specifier types:
// 1️⃣ Literal width
fmt::format("{:10}", 42); // width = 10 (literal)
// 2️⃣ Implicit argument reference for width
fmt::format("{:{}}", 42, 10); // second argument supplies width (implicit)
// 3️⃣ Explicit positional argument reference for precision
fmt::format("{:{2}.{1}}", 42, 3, 5);
// → width = argument #2 (value 5), precision = argument #1 (value 3)
// 4️⃣ Named argument reference (C++20 named arguments)
fmt::format("{:{width}.{prec}}", fmt::arg("width", 8), fmt::arg("prec", 2), 42);
// → width = named argument "width", precision = named argument "prec"
Summary
parse_dynamic_specininclude/fmt/core.hserves as the core parser for dynamic width and precision specifiers in the fmt library.- Literal integers (digits
0-9) are parsed directly viaparse_nonnegative_intwithkindset toarg_id_kind::none. - Implicit references (
{followed by}or:) usectx.next_arg_id()to bind to the next positional argument. - Explicit references delegate to
parse_arg_idanddynamic_spec_handlerto support positional indices ({N}) or named arguments ({name}). - Error handling triggers
"invalid format string"for malformed dynamic specifiers. - The function integrates with
parse_width,parse_precision, and laterhandle_dynamic_specininclude/fmt/format.hto resolve runtime values.
Frequently Asked Questions
How does parse_dynamic_spec distinguish between a literal number and an argument reference?
parse_dynamic_spec checks the first character of the specifier. If it is a digit (0 through 9), the function treats it as a literal and calls parse_nonnegative_int. If the character is an opening brace {, it initiates argument reference parsing to resolve the value dynamically from the argument list.
What is the difference between arg_id_kind::none and arg_id_kind::index in the context of parse_dynamic_spec?
arg_id_kind::none indicates that the width or precision was specified as a literal integer baked into the format string, requiring no runtime argument lookup. arg_id_kind::index (or arg_id_kind::name) indicates that the value must be fetched from a specific argument position or named argument during the formatting phase via handle_dynamic_spec.
Can parse_dynamic_spec handle named arguments for width and precision?
Yes. When the parser encounters a name inside braces (e.g., {width}), parse_arg_id with dynamic_spec_handler records the identifier and sets kind to arg_id_kind::name. The actual value resolution occurs later in handle_dynamic_spec, which looks up the named argument in the format context.
Where does the actual argument value get resolved after parse_dynamic_spec records the reference?
While parse_dynamic_spec (in include/fmt/core.h) only records the reference type and identifier, the actual value resolution happens in detail::handle_dynamic_spec located in include/fmt/format.h (around lines 4040-4052). This function uses the stored kind and ref to extract the integer value from the argument list at formatting time.
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 →