# How fmtlib Implements Compile-Time Format String Validation in C++20

> Learn how fmtlib uses C++20 compile-time validation to catch format string errors during compilation. Discover constexpr parsing and ASTs for robust code.

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

---

**fmtlib implements compile-time format string validation through constexpr parsing utilities in [`include/fmt/compile.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/compile.h) that convert literal format strings into compile-time ASTs, using C++20 features like class non-type template parameters and `if constexpr` to detect syntax errors, mismatched arguments, and invalid specifiers during compilation rather than at runtime.**

The fmtlib/fmt repository provides a zero-overhead formatting library that leverages C++20 capabilities to perform complete format string validation at compile time. This mechanism transforms potential runtime crashes and logic errors into immediate compilation failures with clear diagnostics, ensuring type safety without sacrificing performance.

## The FMT_COMPILE Macro and compiled_string Tag

The entry point for compile-time validation is the `FMT_COMPILE` macro defined in [`include/fmt/compile.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/compile.h). According to the source code at line 38, this macro expands to `FMT_STRING_IMPL(s, fmt::compiled_string)` when the compiler supports `if constexpr` and return-type deduction.

```cpp
#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string)

```

This macro wraps the literal format string `s` in the `compiled_string` tag class. When you invoke `fmt::format` with this tagged type, the overload resolution detects it via `is_compiled_string<S>::value` and routes the call to `detail::compile<T...>(S{})` at line 54, bypassing the runtime parsing path entirely.

## Recursive Constexpr Parsing Architecture

The core validation logic resides in `detail::compile_format_string`, a recursive constexpr function that transforms the string literal into a compile-time AST (Abstract Syntax Tree).

At lines 66-67 of [`include/fmt/compile.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/compile.h), the `detail::compile` function creates a `basic_string_view` of the literal and invokes the parser:

```cpp
constexpr auto result = detail::compile_format_string<
    detail::type_list<Args...>, 0, 0, ...>(fmt);

```

The parser at lines 88-122 examines each character of the format string and constructs AST nodes:

- **Escape sequences** (`{{` or `}}`) become `text` or `code_unit` nodes
- **Replacement fields** (`{}`) become `field` objects
- **Named fields** (`{name}`) become `runtime_named_field` objects  
- **Fields with format specs** (`{:...}`) become `spec_field` objects

## Static Type Checking and Argument Resolution

The parser performs static type validation using `type_list` and template metaprogramming. As shown at lines 19-20, the library maps the *N*-th argument to its deduced type:

```cpp
using type = remove_cvref_t<decltype(detail::get<N>(
    std::declval<Args>()...))>;

```

For named arguments, `get_arg_index_by_name` searches the template argument pack at compile time, enabling checks like "argument 2 must be convertible to `int`". When parsing format specifiers (lines 107-109), the code instantiates `formatter<T, Char>` for the deduced type and validates the specifiers using the same machinery as runtime formatting:

```cpp
auto f = formatter<T, Char>();
auto end = f.parse(ctx);

```

## C++20 Language Features Enabling Validation

The implementation relies on three key C++20 features introduced in [`include/fmt/compile.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/compile.h):

**Class non-type template arguments** allow the literal string itself to become a template parameter via `detail::fixed_string`, enabling full compile-time introspection of the string contents.

**`if constexpr`** drives the branch-heavy parsing logic, discarding unreachable code paths during compilation rather than instantiating them.

**Return-type deduction** (`auto` with `constexpr`) lets the parser return different AST node types (text, field, spec_field) without explicit type names, building a heterogeneous AST tree.

## Error Handling via Constexpr Throw

When the parser encounters malformed syntax—such as unmatched braces, unknown argument names, or type mismatches—it triggers `FMT_THROW(format_error(...))`. Because this occurs within a `constexpr` context, the throw expression produces a compilation error rather than a runtime exception. This mechanism converts format string bugs into hard compilation failures with diagnostic messages pointing directly to the offending format string.

## Building the Compile-Time AST

Helper functions like `make_concat` (lines 60-62) compose parsed fragments into a single compile-time expression:

```cpp
constexpr auto make_concat(L lhs, R rhs) -> concat<L, R>;

```

The resulting object satisfies the `is_compiled_format` concept and provides a `format` method that writes directly to an output iterator. At lines 81-84, the public API simply forwards arguments to this pre-generated code path:

```cpp
cf.format(std::back_inserter(s), args...);

```

No string parsing occurs at runtime, delivering both type safety and optimal performance.

## Practical Usage Examples

### Basic Compile-Time Validation

```cpp
#include <fmt/compile.h>
#include <fmt/core.h>

int main() {
  // Valid: compiles successfully
  std::string s = fmt::format(FMT_COMPILE("Answer: {}"), 42);
  
  // Compile-time error: unmatched brace
  // std::string bad = fmt::format(FMT_COMPILE("Missing {"), 1);
  
  // Compile-time error: type mismatch
  // std::string bad2 = fmt::format(FMT_COMPILE("{:d}"), "text");
}

```

### Named Arguments with Static Checks

```cpp
#include <fmt/compile.h>

int main() {
  std::string s = fmt::format(
      FMT_COMPILE("{greeting}, {name}!"),
      fmt::arg("greeting", "Hello"),
      fmt::arg("name", "World")
  );
  
  // Compile-time error: unknown argument name
  // fmt::format(FMT_COMPILE("{unknown}"), fmt::arg("known", 1));
}

```

### C++20 Literal Operator

```cpp
#include <fmt/compile.h>
using namespace fmt::literals;

int main() {
  // The _cf suffix invokes FMT_COMPILE automatically
  std::string s = fmt::format("{}"_cf, 99);
}

```

## Summary

- **fmtlib** implements compile-time validation in [`include/fmt/compile.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/compile.h) using the `FMT_COMPILE` macro to tag string literals for static analysis.
- The **`detail::compile_format_string`** parser recursively constructs an AST at compile time, distinguishing text, fields, and format specifiers.
- **Static type checking** occurs through `type_list` metaprogramming and `formatter<T, Char>` instantiation, ensuring argument types match format specifiers.
- **C++20 features** including class non-type template arguments, `if constexpr`, and `constexpr` throw enable the parser to fail compilation on malformed input.
- The resulting compiled format object eliminates runtime parsing overhead while guaranteeing type safety.

## Frequently Asked Questions

### What happens when compile-time validation fails?

When the constexpr parser in [`include/fmt/compile.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/compile.h) detects an error—such as mismatched braces, unknown named arguments, or incompatible types—it invokes `FMT_THROW(format_error(...))`. Because this occurs in a constant expression context, the throw causes a compilation error with a diagnostic message describing the format string violation, preventing the program from building until the error is fixed.

### Does compile-time validation work with C++17?

No, this feature requires C++20. The implementation relies on class non-type template parameters to pass the string literal as a template argument, `if constexpr` for compile-time branching, and guaranteed `constexpr` memory allocation for the AST construction. While fmtlib supports C++11 and later, compile-time validation specifically requires C++20 support in both the library and the compiler.

### How does fmtlib's compile-time validation compare to `std::format`?

The C++20 standard library `std::format` performs runtime checking of format strings by default. fmtlib's `FMT_COMPILE` extension provides stricter guarantees by validating syntax and types during compilation, potentially eliminating runtime overhead entirely. According to the fmtlib source, the compiled path generates specialized code that writes directly to output iterators without runtime parsing.

### Can I use compile-time validation with runtime-generated format strings?

No. Compile-time validation requires the format string to be a compile-time constant known when `FMT_COMPILE` is invoked. Runtime strings must use the standard `fmt::format` runtime interface, which performs validation during execution. The library distinguishes these paths through the `is_compiled_string` type trait, routing compile-time strings to `detail::compile` and runtime strings to the standard parser in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h).