# ADL Hook Method for Custom Type Formatting in fmtlib: The Complete Guide

> Learn the ADL hook method for custom type formatting in fmtlib. Discover how to extend fmtlib with your own formatters for compile-time efficiency and zero runtime overhead. Complete guide.

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

---

**The fmtlib library implements a three-tier ADL resolution chain that discovers custom formatters by checking for `fmt::formatter` specializations, `format_as` functions, and `operator<<` overloads, enabling compile-time formatter selection without runtime overhead.**

The `{fmt}` formatting library provides extensible **custom type formatting** through an Argument-Dependent Lookup (ADL) mechanism that requires zero modifications to the library core. As implemented in the `fmtlib/fmt` repository, this **ADL hook method** allows user-defined types to integrate seamlessly with `fmt::format` by providing formatter definitions in their associated namespaces. The resolution process occurs entirely at compile time, ensuring that formatting custom types incurs no additional runtime cost compared to built-in types.

## The Three-Step ADL Resolution Chain

fmtlib discovers formatters through a hierarchical resolution process defined in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) and [`include/fmt/ostream.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ostream.h). When formatting a value of type `T`, the compiler searches for viable formatters in the following order:

1. **Explicit `fmt::formatter<T>` specializations** – The primary template defined in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) allows users to provide total control over formatting logic.
2. **`format_as` delegation** – A lightweight **ADL hook** that converts the custom type to a formatable type without providing a full formatter.
3. **`operator<<` fallback** – Compatibility with legacy stream-based code through the `basic_ostream_formatter` class in [`include/fmt/ostream.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ostream.h).

This ordered approach allows you to choose between fine-grained control and convenient delegation based on specific requirements.

## Method 1: Specializing fmt::formatter for Explicit Control

The most explicit **ADL hook method** involves specializing the primary formatter template found in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h):

```cpp
template <typename T, typename Char>
struct fmt::formatter;

```

When you provide a specialization in the same namespace as your type `T`, normal overload resolution selects it during compilation. This approach offers complete control over parsing format specifiers and generating output.

Consider a `Point` structure that requires custom formatting:

```cpp
namespace myns {
struct Point { int x, y; };

template <> struct fmt::formatter<Point> {
  constexpr auto parse(fmt::format_parse_context& ctx) { 
    return ctx.begin(); 
  }

  template <typename FormatContext>
  auto format(const Point& p, FormatContext& ctx) const {
    return fmt::format_to(ctx.out(), "({},{})", p.x, p.y);
  }
};
}

// Usage
fmt::print("{}", myns::Point{3, 4});   // prints (3,4)

```

## Method 2: Delegating with the format_as ADL Hook

When you want to format a type `T` using the existing formatter for another type `T2`, implement the **`format_as` ADL hook**. According to the source code in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h), the library checks the `has_format_as<T>` trait to detect functions matching:

```cpp
T2 format_as(const T& value);

```

This function must reside in a namespace associated with `T` so that ADL can discover it. The **custom type formatting** system then formats the return value `T2` instead of `T`, effectively delegating the work to an existing formatter.

For example, a `Money` type can delegate to the built-in `double` formatter:

```cpp
namespace lib {
struct Money { double amount; };

inline double format_as(const Money& m) { 
  return m.amount; 
}
}

// Formats via the double formatter
fmt::print("${:.2f}", lib::Money{12.5});   // prints $12.50

```

The `has_format_as<T>` detection mechanism uses SFINAE to verify that `format_as` is callable with `const T&` arguments, ensuring compilation fails gracefully if the hook is malformed.

## Method 3: Fallback Formatting via operator<<

If neither a formatter specialization nor a `format_as` function is available, fmtlib falls back to stream insertion through the **`operator<<` ADL hook**. The generic formatter defined in [`include/fmt/ostream.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ostream.h) inherits from `basic_ostream_formatter<Char>` and is enabled only when the expression:

```cpp
std::declval<std::basic_ostream<Char>&>() << std::declval<const T&>()

```

is well-formed. Because this check uses `std::void_t` and SFINAE, the compiler discovers valid `operator<<` overloads via ADL in the namespace of `T`.

This allows formatting of legacy types without modifying them:

```cpp
namespace gui {
struct Color { int r, g, b; };

inline std::ostream& operator<<(std::ostream& os, const Color& c) {
  return os << '#' << std::hex << c.r << c.g << c.b;
}
}

// ADL finds the << overload automatically
fmt::print("Color: {}", gui::Color{255, 165, 0});   // prints Color: #ffa500

```

The same ADL principles are reused for range and tuple formatters in [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h), demonstrating the extensibility of this architecture across the library.

## Summary

The **ADL hook method** in fmtlib provides three distinct extension points for **custom type formatting**, each resolved at compile time:

- **`fmt::formatter` specialization** – Offers maximum control over parsing and formatting logic, defined in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h).
- **`format_as` function** – Enables delegation to existing formatters via ADL, detected through the `has_format_as<T>` trait.
- **`operator<<` overload** – Provides backward compatibility with stream-based types through `basic_ostream_formatter` in [`include/fmt/ostream.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ostream.h).

These mechanisms allow the library to format any user-defined type while maintaining zero-overhead guarantees and requiring no macro-based registration.

## Frequently Asked Questions

### What is the resolution priority when multiple ADL hooks are available?

fmtlib evaluates **custom type formatting** candidates in a strict hierarchy: `fmt::formatter` specializations take precedence over `format_as` functions, which in turn take precedence over `operator<<` overloads. If a valid formatter specialization exists for type `T`, the compiler immediately selects it and does not consider the alternative **ADL hook method** approaches.

### Can I define format_as in a different namespace than my custom type?

No. Because **ADL hooks** rely on Argument-Dependent Lookup, the `format_as` function must reside in a namespace associated with the type being formatted—typically the same namespace where the class or struct is defined. Defining it elsewhere prevents the compiler from discovering the function during the two-phase lookup process implemented in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h).

### Does the operator<< hook introduce runtime overhead compared to native formatters?

The `operator<<` **ADL hook method** introduces minimal overhead through the `basic_ostream_formatter` implementation in [`include/fmt/ostream.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ostream.h), which wraps the stream insertion operation. While this maintains compatibility with existing code, it may be marginally slower than direct `fmt::formatter` specializations because it involves `std::ostream` state management. For performance-critical paths, prefer implementing a dedicated formatter specialization or using the `format_as` delegation pattern.