How fmtlib's `arg_ref` Union Handles Dynamic Width and Precision in C++

The arg_ref union stores either a positional index or a named identifier to defer width and precision resolution until formatting time, enabling dynamic specifiers like "{:*}" or "{:{w}}".

The fmtlib/fmt repository implements high-performance C++ formatting using compact data structures to manage format specifications. When format strings contain dynamic width or precision arguments—specified with * for positional indices or {name} for named references—the library must store a lightweight reference for later resolution. The arg_ref union defined in include/fmt/core.h serves as this carrier, distinguishing between integer indices and string views without consuming extra memory.

The arg_ref Union Structure

The arg_ref union is a template class defined in include/fmt/core.h (lines 74-81) that efficiently packs two distinct reference types into a single memory location.

template <typename Char> union arg_ref {
  constexpr arg_ref(int idx = 0)       : index(idx) {}
  constexpr arg_ref(basic_string_view<Char> n) : name(n) {}

  int                 index;   // positional reference
  basic_string_view<Char> name; // named reference
};

The union provides implicit constructors for both integral indices and string views, allowing the parser to assign values naturally without explicit type specification. Because index and name share storage, the union remains small—sized to the larger of the two members—while supporting both reference styles required by the C++20 std::format specification.

Storing References in dynamic_format_specs

The formatting engine stores arg_ref instances within the dynamic_format_specs structure, also located in include/fmt/core.h. This structure maintains separate references for width and precision adjustments:

arg_ref<Char> width_ref;      // reference for dynamic width
arg_ref<Char> precision_ref;  // reference for dynamic precision

When encountering a * character in a format string like "{:*}", the parser immediately instantiates an arg_ref and assigns it to width_ref. Similarly, precision specifiers like "{:.{}f}" trigger storage in precision_ref. These references remain unresolved until the actual formatting phase, enabling runtime-dependent layout calculations.

Parsing Dynamic Specifications

The conversion from format string syntax to arg_ref instances occurs in include/fmt/format.h through the parse_dynamic_spec function and its helper class dynamic_spec_handler (lines 71-87).

template <typename Char> struct dynamic_spec_handler {
  parse_context<Char>& ctx;
  arg_ref<Char>&      ref;
  arg_id_kind&        kind;

  constexpr void on_index(int id) {
    ref = id;                     // stores positional index
    kind = arg_id_kind::index;
    ctx.check_arg_id(id);
    ctx.check_dynamic_spec(id);
  }
  constexpr void on_name(basic_string_view<Char> id) {
    ref = id;                     // stores named reference
    kind = arg_id_kind::name;
    ctx.check_arg_id(id);
  }
};

The dynamic_spec_handler receives parsing events and populates the arg_ref accordingly. Positional arguments trigger on_index(), which assigns the integer directly to the union. Named arguments trigger on_name(), storing the basic_string_view. The accompanying arg_id_kind enum tracks which union member is active, ensuring type-safe access later.

Resolving Values at Format Time

When the library renders output, it resolves stored references through handle_dynamic_spec in include/fmt/format.h (lines 39-51). This function bridges the gap between the stored arg_ref and the actual argument values provided by the user.

template <typename Context>
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);
}

The implementation checks the kind parameter to determine whether to access ref.index or ref.name. It retrieves the argument from the formatting context, validates that it contains an integral type, verifies the value fits within integer limits, and finally writes the resolved value into the specification struct. This delayed resolution allows the same format string to produce different output widths based on runtime data without re-parsing.

Practical Usage Examples

Dynamic specifications enable flexible formatting where layout parameters are determined at runtime rather than compile time.

#include <fmt/core.h>

int main() {
    // Positional dynamic width: argument 0 provides width 5
    fmt::print("{:*}\n", 5, 12345);          // prints " 12345"

    // Named dynamic precision: argument "p" provides precision 2
    fmt::print("{:{p}.2f}\n", fmt::arg("p", 2), 3.14159);
    // prints "3.14"
}

In the first call, the * creates a dynamic_format_specs instance with width_ref holding index 0. During formatting, handle_dynamic_spec fetches argument 0 (the value 5) and applies it as the minimum field width.

In the second call, the {p} syntax causes parse_dynamic_spec to store the name "p" in precision_ref. At resolution time, handle_dynamic_spec looks up the named argument, confirms it contains the integer 2, and sets the floating-point precision accordingly.

Summary

  • The arg_ref union in include/fmt/core.h stores either an int index or a basic_string_view name to reference formatting arguments.
  • dynamic_format_specs contains two arg_ref members (width_ref and precision_ref) to support runtime-dependent layout.
  • The dynamic_spec_handler class populates the union during parsing, distinguishing between positional and named references using the arg_id_kind enum.
  • handle_dynamic_spec resolves these references during formatting by querying the context, validating integer types, and applying range checks.
  • This architecture allows fmtlib to support Python-style dynamic width and precision while maintaining zero-overhead abstraction principles.

Frequently Asked Questions

What is the purpose of the arg_ref union in fmtlib?

The arg_ref union provides a type-safe, memory-efficient mechanism to store references to formatting arguments that supply dynamic width or precision values. It consolidates positional indices (integers) and named references (string views) into a single construct, allowing the parser to defer value resolution until the actual formatting phase.

How does fmtlib differentiate between positional and named dynamic arguments?

The library uses a separate arg_id_kind enumeration alongside the arg_ref union to track which member is active. During parsing, dynamic_spec_handler::on_index() sets the kind to arg_id_kind::index, while on_name() sets it to arg_id_kind::name. The handle_dynamic_spec function checks this kind at runtime to determine whether to access ref.index or ref.name.

Where does the actual validation of dynamic width and precision values occur?

Validation occurs in handle_dynamic_spec within include/fmt/format.h. This function verifies that the referenced argument exists, extracts its integral value using dynamic_spec_getter, confirms the value does not exceed max_value<int>(), and reports errors for out-of-range or missing arguments before applying the value to the format specification.

Can arg_ref store types other than integers or string views?

No, the arg_ref union is specifically designed to hold only two types: an int representing a positional argument index, and a basic_string_view<Char> representing a named argument identifier. The actual width or precision values (which may be any integral type) are stored separately in the dynamic_format_specs struct and populated only after resolution through handle_dynamic_spec.

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 →