How to Use `fmt::make_format_args` in C++: A Complete Guide to Building Format Argument Stores

fmt::make_format_args builds a compile-time type-safe argument store that holds references to formatting arguments, enabling zero-copy formatting across the {fmt} library.

The {fmt} library provides fmt::make_format_args as the foundational mechanism for capturing formatting arguments before they are rendered. Defined in include/fmt/core.h, this function creates a detail::format_arg_store object that powers every high-level formatting API in the codebase, from fmt::format to fmt::printf.

What Is fmt::make_format_args?

fmt::make_format_args is a variadic template function that constructs a lightweight container holding references to your formatting arguments. According to the source code in include/fmt/core.h (lines 71-78), the function signature is:

template <typename Context = context, typename... T,
          int NUM_ARGS      = int(sizeof...(T)),
          int NUM_NAMED_ARGS= detail::count_named_args<T...>(),
          ullong DESC      = detail::make_descriptor<Context, T...>()>
constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args)
    -> detail::format_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC> {
  return {{args...}};
}

The template parameters capture compile-time metadata:

  • Context – The formatting context type, defaulting to fmt::context.
  • NUM_ARGS – Compile-time count of positional arguments derived from sizeof...(T).
  • NUM_NAMED_ARGS – Count of named arguments created with fmt::arg, calculated via detail::count_named_args.
  • DESC – A 64-bit compile-time descriptor encoding type information for optimized formatting dispatch.

The function returns a detail::format_arg_store that implicitly converts to fmt::format_args, allowing it to be passed to any API consuming format arguments.

How fmt::make_format_args Works Internally

The implementation stores references (T&... args) rather than copies, which enables zero-allocation formatting paths. This design requires that the original objects outlive the format_args object.

The format_arg_store structure records type information through the DESC template parameter. As implemented in fmtlib/fmt, this descriptor allows the library to select optimal formatting routines at compile time without runtime type introspection.

Internally, fmt::make_format_args serves as the argument forwarding mechanism for higher-level functions. For example, in include/fmt/printf.h (lines 573-574), the printf-style API forwards its arguments through make_format_args:

return fmt::make_format_args<basic_printf_context<Char>>(args...);

Practical Examples

Storing Arguments for Deferred Formatting

Use fmt::make_format_args when you need to capture arguments now but format them later, or when passing arguments through multiple abstraction layers:

#include <fmt/core.h>
#include <string>

int main() {
    int value = 42;
    std::string name = "answer";

    // Build the format argument store.
    auto args = fmt::make_format_args(value, name);

    // Pass to any function accepting fmt::format_args.
    fmt::print("{} is the {}\n", args);  // Output: 42 is the answer
}

Using Named Arguments with fmt::make_format_args

When formatting strings with named placeholders, combine fmt::arg with make_format_args:

#include <fmt/core.h>

int main() {
    int age = 30;
    
    auto args = fmt::make_format_args(
        fmt::arg("name", "Alice"),
        fmt::arg("age", age)
    );

    fmt::print("{name} is {age} years old\n", args);
    // Output: Alice is 30 years old
}

Building Custom Formatting Functions

Create wrapper functions that pre-build argument stores for logging or internationalization pipelines:

#include <fmt/core.h>
#include <fmt/format.h>

template <typename... Ts>
void custom_log(fmt::format_string<Ts...> fmt_str, Ts&&... values) {
    // Construct argument store once.
    auto args = fmt::make_format_args(values...);
    
    // Forward to vprint or custom sinks.
    fmt::vprint(fmt_str.str, args);
}

int main() {
    custom_log("Error {}: {}\n", 404, "Not Found");
}

Integration with {fmt} Library Components

fmt::make_format_args serves as the bridge between user code and the library's internal formatting engine:

  • include/fmt/core.h – Contains the primary definition and format_arg_store implementation.
  • include/fmt/printf.h – Uses make_format_args to adapt C-style printf calls to the type-safe formatting system.
  • include/fmt/xchar.h – Leverages the same mechanism for wide-character (wchar_t) formatting contexts.
  • include/fmt/format.h – High-level functions like fmt::format internally utilize make_format_args to process variadic template arguments.

Lifetime and Safety Considerations

Because fmt::make_format_args captures references to its arguments, you must ensure the referenced objects remain valid through the formatting operation. The format_arg_store does not copy string data or arithmetic values—it merely stores pointers and metadata.

Safe usage:

  • Arguments passed to make_format_args must outlive the resulting format_args object.
  • Do not store the returned format_arg_store beyond the scope of the referenced variables.
  • Temporary objects passed directly to make_format_args are safe only if formatting occurs immediately in the same expression.

Summary

  • fmt::make_format_args creates a zero-copy, type-safe argument container defined in include/fmt/core.h.
  • It returns a detail::format_arg_store that converts to fmt::format_args for use with any formatting function.
  • The function captures compile-time metadata (NUM_ARGS, NUM_NAMED_ARGS, DESC) to optimize formatting performance.
  • It powers internal APIs including fmt::printf (in include/fmt/printf.h) and wide-character formatting.
  • Arguments are stored by reference, requiring careful lifetime management.

Frequently Asked Questions

What is the difference between fmt::make_format_args and variadic templates?

fmt::make_format_args converts runtime values into a type-erased yet type-safe container suitable for APIs that cannot expose template parameters in their interface. While variadic templates preserve types at compile time, make_format_args bundles those types into a format_arg_store that can be passed through virtual functions or stored for later processing, as seen in fmt::vprint implementations.

Does fmt::make_format_args copy my strings and objects?

No. The function stores references (T&...) to the original arguments. This zero-copy design enables high-performance formatting but requires that you manage object lifetimes carefully. The source code in include/fmt/core.h explicitly constructs the store as {{args...}}, preserving reference semantics.

Can I use fmt::make_format_args with fmt::arg for named parameters?

Yes. make_format_args accepts fmt::arg objects directly, and the template machinery automatically counts named arguments via detail::count_named_args. This allows you to build dynamic format strings with named placeholders while maintaining compile-time type safety.

When should I use fmt::make_format_args instead of fmt::format directly?

Use fmt::make_format_args when you need to separate argument capture from formatting execution. Common scenarios include implementing custom logging frameworks, queuing format operations for asynchronous processing, or creating adapter layers between your code and {fmt}'s internal APIs. For immediate string formatting, fmt::format remains the simpler choice.

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 →