How to Use `fmt::format_arg_store` in C++: Dynamic vs Static Argument Storage
fmt::format_arg_store provides two storage mechanisms—dynamic_format_arg_store for runtime argument building and the compile-time format_arg_store generated by make_format_args—that both convert to basic_format_args for low-level formatting functions.
The {fmt} library (fmtlib/fmt) offers flexible utilities for constructing argument lists that can be passed to formatting functions like fmt::vformat and fmt::format_to. Understanding how to use fmt::format_arg_store effectively allows you to build formatting pipelines that minimize allocations and support dynamic, runtime-configurable formatting scenarios.
Understanding the Two Storage Types
The library provides distinct storage strategies depending on whether your argument list is known at compile time or built dynamically at runtime.
Dynamic Storage: dynamic_format_arg_store
The dynamic_format_arg_store<Context> class, defined in include/fmt/args.h, provides a runtime-modifiable store where arguments can be added incrementally using push_back. It automatically manages copies, references, and named arguments, making it ideal for scenarios where the number and types of arguments are determined during program execution.
Static Storage: format_arg_store
The format_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC> template, declared in include/fmt/core.h, represents a compile-time-fixed argument store. Most users never instantiate this type manually; instead, the library generates it automatically when you call fmt::make_format_args. This approach eliminates dynamic allocation entirely by encoding argument counts and types in template parameters.
Both storage types expose an implicit conversion operator to fmt::basic_format_args<Context>, which is the concrete type accepted by the library's low-level formatting APIs.
Runtime Argument Building with dynamic_format_arg_store
For scenarios requiring runtime argument construction, use dynamic_format_arg_store to build argument lists incrementally.
Basic Usage and push_back
The push_back method is overloaded to handle values, references, and named arguments. Arguments that require dynamic allocation (such as std::string or user-defined types) are automatically stored in an internal detail::dynamic_arg_list linked structure, while built-in types and string views are stored by reference when possible.
#include <fmt/args.h>
#include <fmt/core.h>
#include <functional>
#include <string>
int main() {
// Create a store for the default char-based format context
fmt::dynamic_format_arg_store<fmt::format_context> store;
// Push positional arguments by value
store.push_back(42);
store.push_back("hello");
// Push a reference to allow external mutation
std::string mutable_str = "world";
store.push_back(std::cref(mutable_str));
// Push a named argument
store.push_back(fmt::arg("greeting", "Welcome"));
// Format using the store
std::string out = fmt::vformat("{} {} {} {greeting}", store);
// Result: "42 hello world Welcome"
}
Handling Named Arguments and References
Named arguments can reference external variables using std::reference_wrapper, allowing the stored value to reflect changes made after insertion. The template need_copy<T> (lines 78-89 in include/fmt/args.h) determines whether an argument is stored by copy or reference based on its type characteristics.
#include <fmt/args.h>
#include <fmt/core.h>
#include <functional>
int main() {
fmt::dynamic_format_arg_store<fmt::format_context> store;
int count = 5;
// Store named argument by reference
store.push_back(fmt::arg("cnt", std::cref(count)));
// Modify original after insertion
count = 10;
std::string s = fmt::vformat("{cnt}", store);
// Result: "10" (reflects updated value)
}
Compile-Time Storage with make_format_args
When argument types and counts are known at compile time, use fmt::make_format_args to generate a lightweight, allocation-free storage object. This function returns a format_args object that internally uses the static format_arg_store specialization.
#include <fmt/core.h>
void format_with_args() {
// Compiler generates static format_arg_store internally
auto args = fmt::make_format_args(1, 2.5, "three");
// Pass directly to low-level formatting function
std::string result = fmt::vformat("{} {} {}", args);
// Result: "1 2.5 three"
}
This approach is preferred for hot paths where dynamic allocation overhead must be avoided, as the compiler can lay out storage layout without heap operations.
Internal Implementation Details
Understanding the storage mechanics helps optimize usage for performance-critical applications.
Storage Layout
In include/fmt/args.h, dynamic_format_arg_store maintains two primary containers:
data_(vector ofbasic_format_arg<Context>): Holds compact representations of each argumentnamed_info_: Stores metadata mapping argument names to indices (lines 16-27)
When named arguments are added, a placeholder is inserted at the front of data_ and the name-to-index mapping is updated accordingly.
Dynamic Allocation Strategy
Types that cannot fit directly within basic_format_arg are stored in a detail::dynamic_arg_list. The push method creates a typed_node<T> that owns a copy of the argument, linked via std::unique_ptr (head_). This ensures type-safe storage for complex objects while maintaining reference semantics for lightweight types.
Copy vs Reference Logic
The need_copy<T> trait (lines 78-89) implements the following rules:
- Types wrapped in
std::reference_wrapper,string_view, or built-in types are stored by reference - All other types (including
std::stringand user-defined types) are copied into the dynamic list
The conversion operator (lines 33-36) constructs a basic_format_args view over data_, passing the size and a boolean flag indicating the presence of named arguments.
Mixing Positional and Named Arguments
dynamic_format_arg_store supports hybrid formatting strings that combine positional indices and named placeholders.
#include <fmt/args.h>
#include <fmt/core.h>
int main() {
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(100); // Positional argument 0
store.push_back(fmt::arg("city", "Paris")); // Named argument
// Mixing positional and named references
std::string txt = fmt::vformat("{0} is in {city}", store);
// Result: "100 is in Paris"
}
The store maintains separate tracking for positional and named arguments, allowing flexible format string composition without reordering the underlying data structure.
Summary
dynamic_format_arg_storeininclude/fmt/args.hprovides runtime argument building viapush_back, supporting both values and references.make_format_argsgenerates compile-time static storage (format_arg_store) for allocation-free formatting when argument types are known upfront.- Both storage types convert implicitly to
fmt::basic_format_args<Context>for use withfmt::vformatand related functions. - Named arguments use
fmt::arg()and can reference external variables viastd::creffor dynamic value updates. - Internal
need_copy<T>logic optimizes storage by avoiding copies for reference wrappers and view types. - Key implementation files are
include/fmt/args.hfor dynamic storage andinclude/fmt/core.hfor the static variant andmake_format_args.
Frequently Asked Questions
What is the difference between fmt::format_arg_store and fmt::dynamic_format_arg_store?
fmt::format_arg_store is a template class used for compile-time fixed argument lists, automatically instantiated when you call fmt::make_format_args. It encodes argument counts in template parameters and avoids dynamic allocation. fmt::dynamic_format_arg_store is designed for runtime construction, allowing you to add arguments incrementally using push_back and supporting runtime-determined argument counts.
How do I pass arguments by reference to avoid copies?
Wrap your variables in std::reference_wrapper using std::cref or std::ref when calling push_back. The internal need_copy<T> template (defined in include/fmt/args.h) detects reference wrappers and stores them by reference rather than copying them into the dynamic_arg_list. This allows the formatted output to reflect changes to the original variables made after insertion.
Can I mix positional and named arguments in the same format string?
Yes. When using dynamic_format_arg_store, you can push positional arguments with push_back(value) and named arguments with push_back(fmt::arg("name", value)). The format string can then reference both styles simultaneously (e.g., "{0} and {name}"). The store automatically manages the separate indexing systems for positional and named arguments.
When should I use make_format_args instead of dynamic_format_arg_store?
Use make_format_args when the number and types of arguments are known at compile time and you need maximum performance. This approach generates a static format_arg_store with zero heap allocation. Use dynamic_format_arg_store when building argument lists conditionally at runtime, when argument types vary based on program state, or when you need to reuse the same argument set across multiple format calls.
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 →