# How to Format Standard C++ Types Like std::optional and std::variant with fmtlib: A Complete Guide

> Format std::optional and std::variant C++ types with fmtlib easily. Include <fmt/std.h> for automatic human-readable output like optional(42) and variant(hello). Learn more now.

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

---

**You can format `std::optional` and `std::variant` types automatically by including `<fmt/std.h>`, which provides specialized `formatter` templates that output human-readable representations like `optional(42)` or `variant(hello)` without requiring manual to-string conversion.**

The fmt library (fmtlib/fmt) provides first-class support for modern C++ standard library containers through the [`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h) header, enabling type-safe formatting of sum and product types directly via `fmt::format` or `fmt::print`. This article examines the architectural implementation of these formatters and demonstrates how to leverage them in production code.

## Architecture of fmtlib's Standard Library Formatters

The fmt library extends its core formatting engine through template specializations that reside in separate header files to minimize compilation overhead. For standard library vocabulary types, the library provides conditional formatters that verify type support at compile time.

### Optional Formatter Implementation

The `std::optional` formatter is defined in [[`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h)](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h) at lines 121-147, guarded by `#ifdef __cpp_lib_optional` to ensure availability only when the standard library supports optional types.

The implementation uses partial template specialization:

```cpp
template <typename T, typename Char>
struct formatter<std::optional<T>, Char,
                 std::enable_if_t<is_formattable<T, Char>::value>> {
 private:
  formatter<std::remove_cv_t<T>, Char> underlying_;
  static constexpr string_view none = "none";
  static constexpr string_view optional = "optional(";

```

The class stores an **underlying formatter** for the contained type `T`. During the `parse()` phase, it delegates to the underlying formatter after potentially enabling debug format mode via `detail::maybe_set_debug_format(underlying_, true)`.

In the `format()` method, the implementation checks engagement status:

- If `!opt`, it writes the literal `"none"` using `detail::write<Char>(ctx.out(), none)`.
- If engaged, it writes `"optional("`, invokes `underlying_.format(*opt, ctx)` to render the contained value, and appends the closing parenthesis.

View the complete implementation: **[optional formatter source (lines 121-147)](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h#L121-L147)**.

### Variant Formatter Implementation

The `std::variant` formatter begins at line 360 in the same header, activated by `#ifdef __cpp_lib_variant`. This implementation handles heterogeneous types through compile-time type list inspection.

Before formatting, the library verifies that all alternative types are formattable using the `is_variant_formattable` trait (lines 506-514). This trait uses parameter packs and `std::conjunction` to ensure type safety across the variant's type list.

The formatter specialization appears as:

```cpp
template <typename Variant, typename Char>
struct formatter<Variant, Char,
                 std::enable_if_t<std::conjunction_v<
                     is_variant_like<Variant>,
                     detail::is_variant_formattable<Variant, Char>>>> {

```

The `format()` method (lines 369-388) writes the prefix `"variant("`, then uses `std::visit` to dispatch to the active alternative. For each visited value, it calls `detail::write_escaped_alternative<Char>(out, v, ctx)` to apply the appropriate formatter while handling nested types correctly. The implementation includes exception handling for the valueless state, writing `"valueless by exception"` if `std::bad_variant_access` is caught.

View the complete implementation: **[variant formatter source (lines 360-388)](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h#L360-L388)**.

## Practical Usage Examples

To use these formatters, include the standard library extension header and use the standard `fmt::format` API:

```cpp
#include <fmt/core.h>
#include <fmt/std.h>  // Required for std::optional/std::variant support
#include <optional>
#include <variant>
#include <string>

int main() {
    // Formatting std::optional
    std::optional<int> opt_val = 42;
    std::optional<int> opt_empty;
    
    fmt::print("Value: {}\n", opt_val);   // Output: Value: optional(42)
    fmt::print("Empty: {}\n", opt_empty); // Output: Empty: none
    
    // Formatting std::variant
    using Var = std::variant<int, double, std::string>;
    Var v1 = 3.14;
    Var v2 = std::string("fmt");
    
    fmt::print("Variant 1: {}\n", v1);  // Output: Variant 1: variant(3.14)
    fmt::print("Variant 2: {}\n", v2);  // Output: Variant 2: variant(fmt)
    
    // Nested types
    std::optional<Var> nested = Var{100};
    fmt::print("Nested: {}\n", nested);  // Output: Nested: optional(variant(100))
}

```

The formatters automatically handle **debug format specifications** when using the `?` specifier, which quotes strings and escapes characters appropriately for the contained types.

## Key Implementation Details

The standard library formatters in fmtlib demonstrate several architectural best practices:

- **Conditional Compilation**: Formatters are wrapped in feature-test macros (`__cpp_lib_optional`, `__cpp_lib_variant`) to prevent compilation errors on older compilers or standard library implementations.
- **Recursive Formatting**: Both formatters delegate to existing `formatter` specializations for their type parameters, enabling arbitrary nesting depth (e.g., `std::optional<std::variant<std::optional<int>>>`).
- **Exception Safety**: The variant formatter explicitly catches `std::bad_variant_access` to handle degenerate valueless states gracefully.
- **Zero Overhead**: The implementation uses `constexpr` parsing and type erasure only at the format context boundary, maintaining the library's performance characteristics.

## Summary

- Include `<fmt/std.h>` to enable formatting support for `std::optional` and `std::variant` in the fmt library.
- The optional formatter outputs `optional(value)` for engaged optionals or `none` for empty states, utilizing underlying type formatters for recursive rendering.
- The variant formatter uses `std::visit` to dispatch formatting to the active alternative, outputting `variant(value)` while handling valueless-by-exception states.
- Both implementations reside in [`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h) and employ compile-time type checks via `is_formattable` traits to ensure type safety.
- Nested standard library types format automatically through recursive formatter delegation without additional user code.

## Frequently Asked Questions

### Do I need to define custom formatters for std::optional or std::variant?

No, the fmt library provides built-in formatters for these types in the `<fmt/std.h>` header. As long as the contained types are formattable (either built-in or having custom `formatter` specializations), the optional and variant formatters work automatically without additional boilerplate.

### How does fmtlib handle empty optionals or valueless variants?

The optional formatter outputs the string literal `"none"` when the optional is disengaged. For variants, the implementation catches `std::bad_variant_access` and outputs `"valueless by exception"` if the variant enters a degenerate state due to an exception during assignment.

### Can I format nested types like std::optional<std::variant<int, std::string>>?

Yes, the formatters support arbitrary nesting because each delegates formatting to the underlying type's `formatter` specialization. The variant formatter uses `detail::write_escaped_alternative` which recursively applies the correct formatting logic to nested optionals, variants, or other standard containers.

### What C++ standard version is required to use these formatters?

You need a compiler and standard library that support C++17 or later, as indicated by the feature test macros `__cpp_lib_optional` and `__cpp_lib_variant` that guard the formatter definitions in [`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h). The fmt library itself maintains compatibility back to C++11, but these specific formatters require standard library components introduced in C++17.