# How fmtlib Implements Range and Container Formatting in ranges.h

> Discover how fmtlib implements range and container formatting using SFINAE traits and specialized formatters in ranges.h for efficient compile-time detection and customizable output.

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

---

**fmtlib detects range types at compile time using SFINAE-based traits in [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h), then dispatches to specialized `formatter` implementations that handle sequences, associative containers, and strings with customizable delimiters.**

The {fmt} library provides comprehensive support for formatting C++ ranges and containers through a sophisticated template metaprogramming system centralized in [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h). This header implements a multi-layered approach that first classifies types using compile-time introspection, then selects appropriate formatting strategies via the `range_format` enumeration. Understanding how fmtlib implements range and container formatting in ranges.h reveals a design that balances extensibility with zero-overhead abstraction.

## Detecting Range Types with SFINAE Traits

The foundation of fmtlib's range support rests on detecting whether a type provides `begin` and `end` iterators. The library defines several helper traits to probe for these operations across different interface styles.

**Member function detection** uses `has_member_fn_begin_end_t` to identify types defining `begin()` and `end()` as methods. For ADL-discoverable functions, the library provides `has_const_begin_end` and `has_mutable_begin_end` to handle both const-qualified and mutable iteration patterns (lines 62-90).

These individual checks compose into the primary range identification trait:

```cpp
// Simplified representation of the detection logic
detail::is_range_<T>::value  // true if T implements the range interface

```

This trait aggregates the begin/end detection results to mark a type as iterable at compile time, enabling the formatter dispatch system to recognize containers like `std::vector`, `std::list`, or custom range types.

## Excluding Tuples and Optional-Like Types

Not all types with begin/end semantics should use the generic range formatter. The library defines exclusion traits to redirect tuples and optional types to their own specialized formatters.

The `is_tuple_like_` trait checks for `std::tuple_size` and `std::tuple_element` support, identifying `std::tuple`, `std::pair`, and `std::array` types. Meanwhile, `is_optional_like_` detects types providing `has_value()` and `value()` member functions, capturing `std::optional` and similar monadic types (lines 22-38).

These exclusions ensure that code like `fmt::print("{}", std::make_tuple(1, 2))` produces `(1, 2)` rather than `[1, 2]`, maintaining intuitive output formats for distinct C++ idioms.

## Classifying Range Formats with range_format_kind_

Once a type validates as a range, fmtlib categorizes it using `range_format_kind_` to determine the appropriate formatting strategy. This metafunction maps types to the `range_format` enumeration, which defines five distinct categories (lines 52-60):

- **`range_format::sequence`** – Generic iterable containers like vectors and lists
- **`range_format::map`** – Associative containers such as `std::map` and `std::unordered_map`
- **`range_format::set`** – Unique-key containers like `std::set`
- **`range_format::string`** – Character ranges that should display as plain text
- **`range_format::debug_string`** – Character ranges requiring quoted debug output

This classification drives template specialization selection, ensuring that a `std::map<std::string, int>` formats as `{key: value}` while a `std::vector<int>` formats as `[1, 2, 3]`.

## The range_formatter Class Template

For types classified as `range_format::sequence`, the library instantiates `range_formatter`, a class template that iterates through elements and applies their respective formatters. This implementation resides in the primary ranges header and provides extensive customization capabilities (lines 83-95).

The formatter supports runtime customization through method chaining:

```cpp
std::vector<int> v = {1, 2, 3};

// Custom brackets and separator
fmt::print("{:}", fmt::formatter<std::vector<int>>()
                     .set_brackets("<", ">")
                     .set_separator(" | ")
                     .format(v, fmt::format_context{}));
// Output: <1 | 2 | 3>

```

For debug output, the `?s` specifier instructs the formatter to quote string-like ranges, distinguishing the container boundaries from the string content.

## Specialized Handling for Maps and Strings

Associative containers receive specialized treatment when `range_format_kind<R,Char>::value == range_format::map`. The dedicated `formatter<R,Char>` specialization iterates through `std::pair<const Key, T>` elements, formatting each as `key: value` inside braces `{ }` rather than brackets. The `n` format specifier suppresses these delimiters entirely, producing flat output for parsing-friendly scenarios (lines 35-50).

String-like ranges bypass the generic iteration logic. When detected as `range_format::string` or `range_format::debug_string`, formatting forwards directly to `formatter<std::basic_string<Char>>`. For debug strings, the implementation adds surrounding quotes while escaping internal characters, providing Python-style `repr()` functionality (lines 92-100).

```cpp
std::map<std::string, int> m = {{"a", 1}, {"b", 2}};
fmt::print("map = {}\n", m);        // map = {a: 1, b: 2}

std::string s = "hello";
fmt::print("{:?s}\n", s);           // "hello"

```

## Join Views and Container Adaptor Support

Beyond fixed containers, fmtlib provides `fmt::join` for formatting iterator ranges with custom separators. The `join_view` class created by this function stores an iterator pair or container reference along with a separator string. Its associated formatter iterates through the range once, inserting the separator between elements without allocating intermediate strings (lines 122-140):

```cpp
std::vector<int> v = {1, 2, 3};
fmt::print("joined: {}\n", fmt::join(v, " + "));
// Output: joined: 1 + 2 + 3

```

Container adaptors like `std::stack`, `std::queue`, and `std::priority_queue` present special challenges because they obscure their underlying containers. The `is_container_adaptor` trait detects these types by checking for a nested `container_type` member. When identified, the formatter delegates to a view of the underlying container, providing visibility into adaptor contents without breaking encapsulation (lines 70-84).

## Summary

- **Type Detection**: The library uses `has_member_fn_begin_end_t`, `has_const_begin_end`, and `has_mutable_begin_end` to identify range interfaces, aggregated by `detail::is_range_`.
- **Format Classification**: `range_format_kind_` categorizes ranges into sequences, maps, sets, or strings via the `range_format` enumeration.
- **Generic Implementation**: `range_formatter` handles sequences with customizable brackets and separators via `set_brackets()` and `set_separator()`.
- **Specialized Outputs**: Maps format as `{key: value}`, strings forward to dedicated string formatters, and debug mode adds quotes with escaping.
- **Utility Features**: `fmt::join` creates lightweight views for custom separators, while `is_container_adaptor` enables formatting of `std::stack` and similar adaptors.

## Frequently Asked Questions

### How does fmtlib determine if a custom type is a range?

fmtlib checks for `begin()` and `end()` methods using `has_member_fn_begin_end_t`, or ADL-discoverable `begin`/`end` functions via `has_const_begin_end` and `has_mutable_begin_end`. These traits compose into `detail::is_range_<T>`, which evaluates to true if the type provides the standard range interface.

### What distinguishes range_format::map from range_format::sequence?

`range_format::map` triggers a specialized formatter that expects `std::pair` elements and outputs them as `key: value` wrapped in braces `{ }`, whereas `range_format::sequence` uses the generic `range_formatter` that outputs comma-separated values inside brackets `[ ]`.

### Can I customize the output format for a standard vector?

Yes. The `range_formatter` provides `set_brackets()` and `set_separator()` methods to customize delimiters. For one-off formatting with different separators, use `fmt::join(vec, " | ")` instead of formatting the container directly.

### How does fmtlib format container adaptors like std::stack?

The library detects adaptors using `is_container_adaptor`, which checks for a `container_type` member typedef. When found, fmtlib accesses the underlying container through a view and applies standard range formatting, since adaptors themselves do not expose iterators directly.