# How fmtlib Formats C++ Containers and Ranges: Implementation Details

> Discover how fmtlib formats C++ containers and ranges with compile-time detection and specialized templates for efficient, zero-overhead formatting of sequences maps sets and strings.

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

---

**fmtlib formats C++ containers and ranges through a compile-time detection mechanism that classifies types into sequences, maps, sets, or strings, then applies specialized `range_formatter` templates to handle brackets, separators, and element-wise formatting with zero runtime overhead.**

The fmtlib/fmt repository provides automatic formatting capabilities for any range-like type without requiring user-defined formatters. By leveraging template metaprogramming in [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h), the library distinguishes between containers, associative structures, and character sequences to generate optimal formatting code at compile time.

## Range Detection and Classification

The formatting pipeline begins with compile-time type introspection that determines whether a type satisfies the range concept and how it should be presented.

### Detecting Range Types with detail::is_range_

At lines 116-121 of [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h), the `detail::is_range_` trait checks whether a type provides valid `begin` and `end` members or ADL-discoverable free functions. This validation determines if the type satisfies the range concept. Supporting this detection, the helper functions `range_begin` and `range_end` (lines 53-94) normalize access patterns across C-arrays, member functions, and ADL fallbacks, ensuring consistent iteration semantics regardless of the container's interface.

### Classifying Ranges into Categories

Once a type is identified as a range, `detail::range_format_kind_` (lines 152-160) maps it to a specific category defined by the `range_format` enumeration: `disabled`, `map`, `set`, `sequence`, `string`, or `debug_string`. This classification depends on type traits such as `is_map<T>` or `is_set<T>`, allowing the library to apply map-specific formatting (key-value pairs) or string-specific optimizations rather than generic sequence handling.

## Formatter Specializations for Container Types

Each range category receives a tailored `range_formatter` implementation that controls output structure and element presentation.

### Formatting Sequence Containers

For generic sequences where `range_format_kind` yields `range_format::sequence`, the primary template specialization (lines 93-106) requires that element types satisfy `is_formattable<T, Char>`. The `write_body` method (lines 100-115) iterates over the range using `detail::range_begin` and `detail::range_end`, writing user-specified opening and closing brackets while inserting the separator between elements. Each element is formatted recursively using the underlying element formatter.

### Formatting Associative Containers

Associative containers receive dedicated handling based on their classification. For maps (`range_format::map`), the formatter at lines 61-86 treats each element as a key-value pair, utilizing two nested formatters to render entries as `key: value` within braces. Sets use similar logic but format single values rather than pairs, while maintaining the brace delimiters characteristic of associative containers.

### String and Character Container Handling

When `range_format_kind` identifies a character container (yielding `string` or `debug_string`), the implementation at lines 127-144 constructs a `std::basic_string_view` over the range and forwards directly to the standard string formatter. This optimization avoids element-by-element iteration and quotation, treating the container as a single string entity rather than a sequence of characters.

## Customizing Output Format

Users control visual presentation through format specifiers parsed by `range_formatter::parse` (lines 44-78).

### Custom Separators and Brackets

The formatting interface supports runtime customization of delimiters via the `set_separator` and `set_brackets` methods. Format strings like `"{:n}"` omit delimiters entirely (the `n` specifier), while `"{:[}{]}"` instructs the formatter to use curly braces instead of the default square brackets. These specifications modify how `write_body` emits the opening delimiter, separator, and closing delimiter during iteration.

### Using fmt::join for Delimiter-Only Output

The free function `fmt::join` (lines 386-417) creates a lightweight `join_view` that formats a range with only a separator, bypassing default brackets entirely. Unlike standard range formatting which always includes brackets, `join_view` integrates with the formatting infrastructure to emit elements separated by the specified string without surrounding delimiters, ideal for creating comma-separated lists or path-like strings.

## Integration with the Generic formatter API

The public API surface relies on a `formatter<R, Char>` specialization (lines 250-260) that acts as a gateway. When `range_format_kind<R, Char>::value` indicates a supported range (anything other than `disabled`), this specialization forwards to the appropriate `range_formatter` implementation. This design enables seamless usage with `fmt::print` and `fmt::format` while maintaining compile-time resolution of all formatting logic.

## Practical Usage Examples

The following examples demonstrate the automatic formatting capabilities implemented in [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h):

```cpp
#include <fmt/ranges.h>
#include <vector>
#include <map>
#include <list>
#include <array>

int main() {
    // Simple sequence with default brackets and ", " separator
    std::vector<int> v = {1, 2, 3};
    fmt::print("{}\n", v);               // Output: [1, 2, 3]

    // Custom separator and no brackets
    fmt::print("{:n}", fmt::join(v, " | ")); // Output: 1 | 2 | 3

    // Associative container (map) – formats as {key: value, …}
    std::map<std::string, int> m = {{"a",1},{"b",2}};
    fmt::print("{}\n", m);               // Output: {a: 1, b: 2}

    // C‑array formatting
    int a[] = {4,5,6};
    fmt::print("{}\n", a);               // Output: [4, 5, 6]

    // std::list with custom brackets
    std::list<char> lst = {'x','y','z'};
    fmt::print("{:[}{]}", lst);          // Output: {x, y, z}
}

```

## Summary

- **Compile-time detection** via `detail::is_range_` and `range_begin`/`range_end` helpers enables automatic support for any type providing standard iteration interfaces.
- **Classification system** using `range_format_kind_` distinguishes sequences, maps, sets, and strings to apply appropriate formatting logic.
- **Specialized formatters** including `range_formatter` for sequences and dedicated handlers for associative containers manage bracket placement and element iteration.
- **Customizable output** through format specifiers parsed by `range_formatter::parse` allows modification of separators and brackets without custom code.
- **`fmt::join` utility** provides delimiter-only formatting through `join_view`, bypassing brackets for specific use cases.
- **Zero-overhead integration** via the `formatter<R, Char>` specialization ensures all logic resolves at compile time with no runtime cost.

## Frequently Asked Questions

### How does fmtlib detect if a type is a range?

fmtlib uses the `detail::is_range_` trait defined at lines 116-121 of [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h) to check for valid `begin` and `end` members or ADL-discoverable free functions. The `range_begin` and `range_end` helpers (lines 53-94) then normalize access across C-arrays, member functions, and ADL fallbacks to provide a consistent iteration interface.

### Can I format custom containers that are not STL types?

Yes. Any user-defined type that provides `begin()` and `end()` members or has ADL-discoverable `begin`/`end` free functions will be automatically detected as a range by `detail::is_range_`. As long as the element type is formattable, the `range_formatter` will handle the container without requiring a custom formatter specialization.

### What is the difference between formatting a container normally and using `fmt::join`?

Standard range formatting through `range_formatter` automatically adds brackets (square by default) and separators between elements. In contrast, `fmt::join` (lines 386-417) creates a `join_view` that formats the range with only the specified separator, omitting brackets entirely. Use `fmt::join` when you need comma-separated values without surrounding delimiters.

### How do I change the brackets and separators for a specific formatting call?

You can customize delimiters using format specifiers parsed by `range_formatter::parse` (lines 44-78). Use `"{:n}"` to omit brackets entirely, or `"{:[}{]}"` to specify custom opening and closing brackets. The `set_separator` and `set_brackets` methods configure these properties programmatically within the formatter.