# How fmtlib Handles Dynamic Width and Precision in Format Strings

> Discover how fmtlib handles dynamic width and precision in format strings. Learn about argument references and the handle_dynamic_spec function for flexible formatting.

- Repository: [Hello World Foundation/fmt](https://github.com/fmtlib/fmt)
- Tags: internals
- Published: 2026-09-09

---

**The fmt library implements dynamic width and precision by storing argument references during parsing and resolving them to concrete integer values at formatting time through the `handle_dynamic_spec` function.**

The {fmt} library (also known as fmtlib) provides the formatting foundation for C++20's `std::format`. When you need to specify field width or precision at runtime rather than compile time, fmtlib uses a two-phase architecture that separates static parsing from dynamic value resolution, allowing format strings to be parsed once and reused with different argument sets.

## Parsing Phase: Recording Dynamic Specifications

When parsing a format specifier like `{:{}}` or `{:.{}}`, the library encounters dynamic width or precision markers that require runtime values. Instead of storing literal integers, the parser records references to the arguments that will supply these values later.

### The parse_dynamic_spec Function

Inside [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h), the functions `parse_width` and `parse_precision` delegate dynamic value detection to `parse_dynamic_spec`. This helper inspects the next token to determine if it represents a static integer or a dynamic argument reference. When it encounters an opening brace `{`, it creates an `arg_ref<Char>` object that stores either the positional index (`arg_id_kind::index`) or named identifier (`arg_id_kind::name`) of the source argument.

The `dynamic_format_specs` struct (lines 1286-1289 in [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h)) holds these references:

```cpp
struct dynamic_format_specs {
  arg_ref<Char> width_ref;       // dynamic width source
  arg_ref<Char> precision_ref;   // dynamic precision source
  // ... other members
};

```

### Argument Reference Storage

The `arg_ref` type acts as a lightweight placeholder. By storing the argument identifier rather than the value itself, the library defers the actual lookup until formatting time. This design enables the same compiled format string to work with different width and precision values across multiple formatting calls without re-parsing.

## Formatting Phase: Resolving Dynamic Values

Once parsing completes, the formatting context contains the actual argument values. The library then resolves the stored references to concrete integers before applying them to the output.

### The handle_dynamic_spec Function

Defined in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) (lines 39-51), `handle_dynamic_spec` performs the runtime resolution. It accepts the context object, retrieves the referenced argument using `ctx.arg()`, and validates that the extracted value fits within the range of a signed integer:

```cpp
template <typename Context>
FMT_CONSTEXPR void handle_dynamic_spec(arg_id_kind kind,
                                       int& value,
                                       const arg_ref<typename Context::char_type>& ref,
                                       Context& ctx) {
  if (kind == arg_id_kind::none) return;
  auto arg = kind == arg_id_kind::index ? ctx.arg(ref.index) : ctx.arg(ref.name);
  if (!arg) report_error("argument not found");
  ullong result = arg.visit(dynamic_spec_getter());
  if (result > to_unsigned(max_value<int>()))
    report_error("width/precision is out of range");
  value = static_cast<int>(result);
}

```

### Value Application and Validation

The `dynamic_spec_getter` visitor extracts the unsigned integer value from the argument. If the value exceeds `max_value<int>()` or the referenced argument is missing, `report_error` throws an appropriate exception. After successful resolution, the integer is written into the `width` or `precision` fields of the `format_specs` object, allowing the formatter to proceed as if these values had been specified statically.

## Practical Code Examples

The following examples demonstrate dynamic width and precision using positional and named arguments:

```cpp
// Dynamic width specified at runtime
int w = 10;
fmt::print("{:{}}", 42, w);   // prints "        42"

```

```cpp
// Dynamic precision for floating-point output
double pi = 3.14159;
fmt::print("{:.{}}", pi, 2);   // prints "3.14"

```

```cpp
// Dynamic width and precision with named arguments
fmt::print("{:{width}.{prec}}", 7.12345,
           fmt::arg("width", 8), fmt::arg("prec", 3)); 
// prints "   7.123"

```

## Summary

- **Two-phase architecture**: fmtlib separates parsing (storing `arg_ref` references in `dynamic_format_specs`) from formatting (resolving values via `handle_dynamic_spec`) to maximize performance and reusability.
- **Source locations**: Dynamic specification parsing occurs in [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h), while value resolution is implemented in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h).
- **Type safety**: The `handle_dynamic_spec` function validates that resolved values fit within integer ranges and that referenced arguments exist.
- **Flexibility**: The system supports both positional indices and named arguments for dynamic specifiers, using `arg_id_kind` to distinguish between them.

## Frequently Asked Questions

### What is the performance cost of using dynamic width or precision in fmtlib?

The overhead is minimal because fmtlib parses the format string once and stores only lightweight `arg_ref` objects. The actual value lookup happens once per format operation via `handle_dynamic_spec`, which performs a simple argument table access and bounds check. This is significantly faster than re-parsing the entire format string for each call.

### Can I mix static and dynamic specifiers in the same format string?

Yes. According to the source code in [`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h), the parser processes each specifier independently. You can combine literal values with dynamic references in the same format string, such as `{:10.{}}` (static width, dynamic precision) or `{:{}.2}` (dynamic width, static precision).

### How does fmtlib handle out-of-range dynamic width or precision values?

The `handle_dynamic_spec` function in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) validates that the resolved unsigned long long value does not exceed `to_unsigned(max_value<int>())`. If the value is too large, the library calls `report_error("width/precision is out of range")`, which typically throws a `format_error` exception.

### Are named arguments supported for dynamic width and precision?

Yes. When the format string uses named references like `{:{width}}`, the parser stores `arg_id_kind::name` in the `arg_ref` structure. During formatting, `handle_dynamic_spec` resolves the value by calling `ctx.arg(ref.name)` instead of `ctx.arg(ref.index)`, enabling fully dynamic formatting with named parameters.