How basic_format_context Retrieves Dynamic Width and Precision Values in fmtlib

fmtlib stores dynamic width and precision as argument identifiers during parsing, then retrieves the actual integer values from basic_format_context at formatting time using handle_dynamic_spec.

The fmt library implements dynamic formatting through a deferred resolution mechanism. When a format string contains runtime-dependent width or precision specifiers using the * syntax, basic_format_context (defined in include/fmt/core.h) serves as the execution environment that bridges parsed format specifications with the actual argument values provided at the call site.

Understanding Dynamic Format Specifiers

Dynamic width and precision allow format strings to accept field dimensions as runtime arguments rather than compile-time constants.

What Are Dynamic Width and Precision?

In fmtlib, dynamic specifiers use the * character or named references to indicate that an argument supplies the numeric value. For example, "{:*>{}}" uses the next argument for width, while "{:{width}}" references a named argument. These are distinct from static specifiers like "{:10}" where the value 10 is embedded directly in the format string.

The Role of basic_format_context

The basic_format_context type is a conditional alias that resolves to either the default context (when using internal appenders) or generic_context<OutputIt, Char> (for user-provided output iterators). According to the source in include/fmt/core.h around line 620, this alias determines how the formatting backend accesses arguments:

using basic_format_context =
    conditional_t<std::is_same<OutputIt, appender>::value, context,
                  generic_context<OutputIt, Char>>;

This context object provides the arg() method used to retrieve dynamic values during the formatting phase.

The Three-Stage Retrieval Process

The mechanism operates through three distinct phases: parsing, detection, and resolution.

Stage 1: Parsing the Format String

When the parser encounters a dynamic specifier in include/fmt/format.h, the parse_width or parse_precision functions store a reference to the argument in specs.width_ref_ or specs.precision_ref_. The parser records whether the reference is by index or name using arg_id_kind and invokes specs.set_dynamic_width() or specs.set_dynamic_precision() to flag these fields as dynamic.

For indexed arguments, the parser stores arg_id_kind::index; for named arguments, it stores arg_id_kind::name. This information populates the arg_ref structure within the dynamic_format_specs object.

Stage 2: Detecting Dynamic Specifications

After parsing completes, the formatting infrastructure checks specs.dynamic() (defined in include/fmt/format.h near line 4019) to determine if any width or precision values require runtime resolution. This method inspects internal bit-fields (width_mask and precision_mask) in the basic_specs structure to identify which fields contain argument identifiers rather than literal integers.

Stage 3: Resolving Values with handle_dynamic_spec

The core retrieval logic resides in handle_dynamic_spec, implemented in include/fmt/format.h around line 4039. This function accepts:

  • The arg_id_kind (index or name)
  • A reference to the target field (int& for specs.width or specs.precision)
  • The stored arg_ref containing the identifier
  • The current Context (specialization of basic_format_context)

handle_dynamic_spec retrieves the argument via ctx.arg(ref.index) or ctx.arg(ref.name), then applies dynamic_spec_getter to convert the argument to an unsigned long long. If the argument is not an integral type, the library raises an error via report_error("width/precision is not integer"). After validation, the function writes the concrete integer back into the specification's width or precision field.

Implementation Details in the Source Code

Several key components enable this deferred resolution architecture.

The basic_format_context Alias

As defined in include/fmt/core.h, basic_format_context abstracts the argument access interface. The generic_context specialization provides the arg() member function that handle_dynamic_spec invokes to fetch dynamic values from the argument list.

Argument References and arg_ref

The arg_ref structure (contained within dynamic_format_specs in include/fmt/format.h) stores either a numeric index or a string name. This lightweight struct is populated during parsing and consumed during formatting to locate the correct argument within the context.

Type Safety and Validation

The dynamic_spec_getter visitor ensures type safety by accepting only integral types. When handle_dynamic_spec calls arg.visit(dynamic_spec_getter()), the visitor extracts the value as unsigned long long. The implementation validates that the value fits within the expected range before assigning it to the specification's integer fields.

Practical Code Examples

The following examples demonstrate dynamic width and precision resolution through basic_format_context:

// Dynamic width using positional argument
fmt::print("{:{}}", 42, 5);  // Retrieves width=5 from second argument
// Output: "   42"

// Dynamic precision for floating-point output
fmt::print("{:.{}f}", 3.14159, 2);  // Retrieves precision=2
// Output: "3.14"

// Dynamic width using named arguments (C++20 syntax)
fmt::print("{:{width}}", 7, fmt::arg("width", 8));
// Retrieves width=8 from named argument
// Output: "       7"

In each case, the parser stores the argument reference during format string compilation, and basic_format_context resolves the value through handle_dynamic_spec at output generation time.

Summary

  • Parsing phase: parse_width and parse_precision in include/fmt/format.h store argument identifiers in specs.width_ref_ or specs.precision_ref_ when encountering * syntax.
  • Detection: The dynamic() method checks bit-fields in basic_specs to identify which values require runtime lookup.
  • Resolution: handle_dynamic_spec uses basic_format_context::arg() to fetch arguments, converts them via dynamic_spec_getter, and validates types before assigning to width/precision fields.
  • Type safety: Non-integer arguments trigger errors through report_error.

Frequently Asked Questions

How does basic_format_context differ from generic_context?

basic_format_context is a type alias that conditionally resolves to either the default context type or generic_context<OutputIt, Char> depending on the output iterator. In include/fmt/core.h, this distinction allows the library to optimize for the common case of internal appenders while supporting user-defined output iterators through generic_context.

What happens if a dynamic width argument is not an integer?

The handle_dynamic_spec function applies dynamic_spec_getter as a visitor to the retrieved argument. If the argument type is not an integral type, the library invokes report_error("width/precision is not integer"), preventing invalid width or precision values from corrupting the output format.

Can dynamic width use named arguments instead of indices?

Yes. When parsing named references like {:{width}}, the parser stores arg_id_kind::name in the specification and populates the arg_ref with the name string. During formatting, handle_dynamic_spec calls ctx.arg(ref.name) to resolve the value from the named argument map.

Which header files contain the dynamic width implementation?

The primary implementation spans two headers: include/fmt/core.h defines the basic_format_context alias and argument access infrastructure, while include/fmt/format.h implements the parsing logic (parse_width, parse_precision), the handle_dynamic_spec resolution function, and the dynamic_format_specs structure.

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 →