# Understanding Type Deduction for `format_string<T...>` in `fmt::print` and `fmt::format`

> Learn how format_string<T...> in fmt::print and fmt::format enables compile-time validation while allowing flexible type deduction for your arguments.

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

---

**In the fmt library, `format_string<T...>` prevents the format string literal from participating in template argument deduction while allowing the compiler to deduce argument types `T...` from trailing parameters, enabling compile-time format string validation.**

The {fmt} library (repository `fmtlib/fmt`) implements a sophisticated template mechanism that separates format string validation from argument type deduction. This design ensures that malformed format strings trigger static assertions at compile time rather than runtime errors, all while maintaining an intuitive API for `fmt::print` and `fmt::format`.

## The `fstring` Foundation in base.h

The mechanism centers on the `fstring` class template defined in [`include/fmt/base.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/base.h) at lines 2696 through 2744. This class wraps a `string_view` representing the format literal and encapsulates the compile-time validation logic.

Within the `fstring<T...>` constructor (lines 2711–2722), the library invokes `detail::parse_format_string` with a type-aware checker. This checker validates that the number and types of placeholders in the literal match the template parameter pack `T...`. If the literal contains mismatched braces or incompatible specifiers, the validation triggers a `static_assert`, producing a clear compiler error immediately.

## The `format_string` Alias and Non-Deduced Contexts

At line 2745 of [`include/fmt/base.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/base.h), the library defines the user-facing type:

```cpp
template <typename... T>
using format_string = typename fstring<T...>::t;

```

This alias template is the critical component that controls type deduction. Because `format_string<T...>` is an alias-dependent type, it constitutes a **non-deduced context** according to C++ template deduction rules. When the compiler encounters a function parameter of this type, it cannot deduce `T...` from the format string argument itself. Instead, deduction must occur from subsequent parameters.

## How Deduction Works at the Call Site

The function signatures in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) (lines 2828–2831 for `print`, and lines 4569–4572 for `format`) exploit this non-deduced context:

```cpp
template <typename... T>
void print(format_string<T...> fmt, T&&... args);

```

Consider the following invocation:

```cpp
fmt::print("{} {}", 42, std::string{"hello"});

```

The compiler performs deduction by examining only the trailing arguments `42` and `std::string{"hello"}`, deducing `T...` as `<int, std::string>`. Because `format_string<T...>` is non-deduced, the string literal `"{}{}"` does not influence template argument deduction. The compiler merely verifies that the literal can convert to the already-deduced `format_string<int, std::string>` type.

## Compile-Time Validation Guarantees

Once `T...` is deduced, the constructor of `fstring<T...>` validates the format string against these specific types. This two-phase process—deduction followed by validation—enables the library to catch errors like the following at compile time:

```cpp
// ❌ Compile-time error: too few arguments for the format string
fmt::print("{} {}", 42);

```

The validation ensures that every `{}` placeholder has a corresponding argument of compatible type, eliminating runtime format string parsing overhead and preventing security vulnerabilities associated with malformed format strings.

## Wide-Character Support with `wformat_string`

The same deduction pattern applies to wide-character formatting in [`include/fmt/xchar.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/xchar.h). The library defines:

```cpp
using wformat_string = typename basic_format_string<wchar_t, T...>::t;

```

Overload sets in [`xchar.h`](https://github.com/fmtlib/fmt/blob/main/xchar.h) mirror those in [`format.h`](https://github.com/fmtlib/fmt/blob/main/format.h), applying identical non-deduced context rules to `wchar_t` format strings. This ensures consistent compile-time checking regardless of character width.

## Runtime Format String Escape Hatch

When compile-time checking is impossible—such as when using user input—the library provides `fmt::runtime()`. This wrapper constructs a `format_string` without triggering the compile-time validator:

```cpp
// Runtime format string (no compile-time validation)
fmt::print(fmt::runtime(user_input), 42);

```

This API design forces developers to explicitly opt out of safety checks, maintaining the default behavior of static verification for string literals.

## Summary

- **`format_string<T...>`** acts as a non-deduced context alias, defined at line 2745 of [`include/fmt/base.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/base.h).
- **Template argument deduction** occurs exclusively from trailing function arguments (`T&&... args`), not from the format string parameter.
- **`fstring<T...>`** performs compile-time validation during construction (lines 2711–2722 of [`base.h`](https://github.com/fmtlib/fmt/blob/main/base.h)), comparing the literal against deduced types `T...`.
- **Function overloads** in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) (lines 2828–2831 and 4569–4572) combine these components to provide type-safe formatting.
- **Wide-character variants** in [`include/fmt/xchar.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/xchar.h) use `basic_format_string<wchar_t, T...>` with identical deduction semantics.

## Frequently Asked Questions

### Why does the format string not participate in template argument deduction?

Because `format_string<T...>` is an alias template dependent on `fstring<T...>::t`, it constitutes a non-deduced context under C++ standard rules. This intentional design forces the compiler to deduce `T...` only from the trailing arguments pack, ensuring the format string literal is validated against already-determined types rather than influencing their deduction.

### What happens if I pass a runtime-generated string to `fmt::print`?

Without `fmt::runtime()`, passing a non-constexpr string causes a compilation error because `fstring` requires a compile-time constant for validation. You must explicitly wrap runtime strings with `fmt::runtime()` to bypass static checking, creating a `format_string` with deferred runtime parsing.

### Where does the actual format string validation occur?

Validation happens in the constructor of `fstring<T...>` at lines 2711–2722 of [`include/fmt/base.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/base.h). This constructor calls `detail::parse_format_string` with a type-aware checker that verifies placeholder counts and specifiers match the deduced parameter pack `T...` before any formatting occurs.

### How does wide-character formatting differ in its type deduction?

Wide-character formatting uses `wformat_string<T...>`, defined in [`include/fmt/xchar.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/xchar.h) as an alias for `basic_format_string<wchar_t, T...>::t`. The deduction mechanism remains identical to the narrow-character version, treating the format string as a non-deduced context while extracting types from trailing arguments.