How to Use Positional and Named Arguments with fmtlib: A Complete Guide
Use {} or {0} for positional arguments and fmt::arg("name", value) for named arguments, then reference them as {name} in your format string.
fmtlib provides flexible, type-safe formatting through two argument referencing styles. The library's implementation in fmtlib/fmt allows you to mix positional indices, automatic sequencing, and named parameters within a single format string. This article breaks down the source-level mechanics and practical usage patterns.
Understanding Positional Arguments
Positional arguments in fmtlib work through explicit indexing or automatic ordering. The library resolves these at compile time for maximum performance.
Explicit and Implicit Positioning
You have two syntax options for positional arguments:
{N}— explicit zero-based index (e.g.,{0},{1}){}— implicit next argument in order
#include <fmt/core.h>
int main() {
// Implicit order: arguments consumed left-to-right
std::string s1 = fmt::format("First: {}, Second: {}", 10, 20);
// Result: "First: 10, Second: 20"
// Explicit indices: reuse arguments or reorder them
std::string s2 = fmt::format("First: {0}, Second: {1}", 10, 20);
std::string s3 = fmt::format("Second: {1}, First: {0}", 10, 20);
// Result: "Second: 20, First: 10"
}
In include/fmt/core.h, the parser extracts arguments directly from the parameter pack using the specified index. The none index constant triggers sequential consumption for {} placeholders.
Using Named Arguments with fmtlib
Named arguments improve code readability and allow flexible parameter ordering. The implementation carries runtime name metadata for lookup.
Creating Named Arguments with fmt::arg
The helper function fmt::arg (defined around line 2799 in include/fmt/format.h) constructs a named_arg<T> wrapper:
#include <fmt/core.h>
int main() {
std::string s = fmt::format(
"Name: {name}, Age: {age}",
fmt::arg("name", "Alice"),
fmt::arg("age", 30)
);
// Result: "Name: Alice, Age: 30"
}
The named_arg Implementation
The underlying structure in include/fmt/core.h binds a name string to a value reference:
template <typename T, typename Char = char>
struct named_arg : view {
const Char* name; // argument name for runtime lookup
const T& value; // reference to actual value
named_arg(const Char* n, const T& v) : name(n), value(v) {}
static_assert(!is_named_arg<T>::value, "nested named arguments");
};
The named_arg_store built by make_format_args creates a lookup table of named_arg_info entries mapping names to indices.
Mixing Positional and Named Arguments
fmtlib allows hybrid format strings combining both styles. The internal argument store maintains separate counters for each type.
#include <fmt/core.h>
int main() {
// Positional {0} mixed with named {age}
std::string s = fmt::format(
"{0} is {age} years old",
"Bob", // positional argument 0
fmt::arg("age", 45) // named argument
);
// Result: "Bob is 45 years old"
// Multiple positionals with named parameters interspersed
std::string report = fmt::format(
"{0}: {1} scored {points} points in quarter {quarter}",
"Game Update", // {0}
"Lakers", // {1}
fmt::arg("points", 87),
fmt::arg("quarter", 3)
);
}
The parser in include/fmt/core.h (around line 2629) performs runtime name lookup by iterating named_arg_info entries when encountering a named placeholder.
Wide-Character Support
Named arguments work with wchar_t strings through overloads in include/fmt/xchar.h:
#include <fmt/xchar.h>
int main() {
std::wstring ws = fmt::format(
L"Coordinates: ({x}, {y})",
fmt::arg(L"x", 3.5),
fmt::arg(L"y", 7.2)
);
// Result: L"Coordinates: (3.5, 7.2)"
}
Use the L prefix consistently for both format strings and argument names.
Error Handling and Validation
fmtlib provides compile-time safety for common errors:
- Duplicate names — detected at compile time via
report_error("duplicate named arg")ininclude/fmt/core.h - Missing arguments — compile-time error when a referenced index or name lacks a corresponding value
- Type mismatches — caught by the formatter's type-erasure mechanism
The compile.h header provides additional static checks when names must be known at compile time.
Summary
- Positional arguments use
{N}for explicit indices or{}for automatic sequencing - Named arguments require
fmt::arg("name", value)and are referenced as{name} - Mixing styles is fully supported with separate internal tracking
- Implementation files:
core.h(parsing,named_arg),format.h(public API,fmt::arg),xchar.h(wide character support) - Performance: positional arguments resolve at compile time; named arguments use lightweight runtime lookup
Frequently Asked Questions
What is the performance cost of named arguments versus positional arguments?
Named arguments incur a small runtime overhead for name-to-index lookup via the named_arg_info table. Positional arguments resolve entirely at compile time. For hot paths, prefer positional syntax; for readability in message formatting, named arguments are optimal.
Can I reuse the same named argument multiple times in a format string?
Yes. Once defined with fmt::arg("name", value), you can reference {name} any number of times in the format string. The lookup occurs per placeholder, retrieving the same cached index each time.
Does fmtlib detect duplicate named argument definitions?
Yes. Defining fmt::arg("x", a) and fmt::arg("x", b) in the same call triggers a compile-time error through report_error("duplicate named arg") in the init_named_arg logic within include/fmt/core.h.
How does fmtlib handle named arguments with custom types?
Custom types work transparently when they provide a formatter<T> specialization. The named_arg template stores a const T&, so your formatter receives the value directly without extra wrapping. The type-checking occurs at the same point as positional arguments.
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 →