# How to Format Containers and Ranges Using fmtlib ranges.h

> Learn to format C++ containers and ranges using fmtlib's ranges.h. Easily print sequences, maps, sets, and strings with automatic type detection for cleaner code.

- Repository: [Hello World Foundation/fmt](https://github.com/fmtlib/fmt)
- Tags: how-to-guide
- Published: 2026-09-04

---

**You can format C++ containers, maps, tuples, and ranges by including `<fmt/ranges.h>`, which provides automatic type detection and specialized formatters for sequences, maps, sets, and strings.**

The **fmtlib/fmt** library extends its formatting capabilities to standard containers and ranges through the [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h) header. This module automatically detects iterable types and applies specialized formatting strategies based on whether the type represents a sequence, map, set, or string. Understanding how [`ranges.h`](https://github.com/fmtlib/fmt/blob/main/ranges.h) classifies and formats these types allows you to print complex data structures with minimal boilerplate while maintaining full control over output styling.

## How Range Detection Works

The library determines whether a type qualifies as a range by inspecting its iterator interface at compile time.

### Member Function Detection

In [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h) (lines 95-115), the implementation checks for `begin()` and `end()` member functions using `detail::has_const_begin_end` and `detail::has_mutable_begin_end` type traits. If the container provides these accessors, the library considers it a valid range for formatting.

### ADL Fallbacks

When member functions are absent, the library falls back to argument-dependent lookup via `range_begin` and `range_end` functions. This mechanism enables formatting of C-style arrays and third-party types that define free `begin`/`end` functions in their associated namespaces.

## Range Format Kinds and Classification

Once a type is identified as a range, `detail::range_format_kind_` (lines 252-260) classifies it into one of five categories to determine the appropriate formatting strategy:

- **Sequence**: Default for iterable containers like `std::vector` and `std::list`
- **Map**: Types with both `key_type` and `mapped_type`, such as `std::map` and `std::unordered_map`
- **Set**: Types with `key_type` but no `mapped_type`, such as `std::set`
- **String**: Character ranges like `std::string` and `std::string_view`
- **Debug string**: Character ranges with escaped output for debugging purposes

## Formatting Different Container Types

The `formatter` specializations in [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h) handle each category with specific bracket and separator conventions.

### Sequences and Vectors

For standard sequences, the generic range formatter (lines 380-499) encloses elements in square brackets with comma separators:

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

std::vector<int> v{1, 2, 3};
fmt::print("Values: {}\n", v);  // Output: Values: [1, 2, 3]

```

### Maps and Sets

The map formatter (lines 520-590) outputs key-value pairs with a colon separator, while sets follow the sequence bracket style:

```cpp
#include <map>
#include <set>

std::map<std::string, int> m{{"a", 1}, {"b", 2}};
std::set<int> s{1, 2, 3};

fmt::print("Map: {}\n", m);  // Output: Map: {a: 1, b: 2}
fmt::print("Set: {}\n", s);  // Output: Set: {1, 2, 3}

```

Maps use curly braces `{}` while sets also use curly braces to distinguish them from indexed sequences.

### Tuples and Pairs

Tuple-like types use a dedicated formatter (lines 660-730) that treats them as fixed-size collections with parentheses:

```cpp
auto tup = std::make_tuple(42, "hello", 3.14);
fmt::print("Tuple: {}\n", tup);  // Output: Tuple: (42, hello, 3.14)

```

The implementation uses `tuple::get_formatters` to format each element according to its individual type.

### Strings and Debug Strings

Character ranges can format as plain text or debug strings. The string formatter (lines 600-630) handles normal output, while the debug string mode adds quotes and escaping:

```cpp
std::string str = "hello\n";
fmt::print("{}\n", str);      // Output: hello (with newline)
fmt::print("{:?}\n", str);    // Output: "hello\n" (quoted and escaped)

```

## Using fmt::join for Custom Separators

The `fmt::join` utility creates a lightweight view (lines 322-382) that formats any range with a user-provided separator:

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

```

Unlike the default range formatter, `join` does not add brackets automatically, making it ideal for creating CSV-style output or custom delimited lists.

## Container Adaptors

The library provides special handling for container adaptors like `std::stack`, `std::queue`, and `std::priority_queue`. Through `is_container_adaptor` (lines 740-804), the formatter accesses the underlying container and applies standard range formatting rules to its elements.

```cpp
#include <stack>

std::stack<int> stk;
stk.push(1); stk.push(2);
fmt::print("{:n}\n", stk);  // Accesses underlying container, outputs without outer brackets

```

## Customizing Output with Format Specifiers

The range formatter supports several format specifiers that modify output behavior:

- **`n`**: Remove outer brackets (e.g., `{:n}` on a vector prints `1, 2, 3` instead of `[1, 2, 3]`)
- **`?s`**: Force debug string mode for character ranges
- **Custom delimiters**: Use `fmt::join` for arbitrary separators between elements

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

```

## Summary

- Include **[`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h)** to enable automatic formatting of containers, ranges, and tuples
- The library uses `detail::has_const_begin_end` and ADL-based `range_begin`/`range_end` to detect iterable types
- `detail::range_format_kind_` classifies ranges as sequences, maps, sets, or strings for appropriate formatting
- Maps render as `{key: value}` pairs, tuples as `(a, b, c)`, and sequences as `[a, b, c]`
- Use **`fmt::join`** for custom separators without outer brackets
- Container adaptors like `std::stack` are automatically unwrapped and formatted via `is_container_adaptor`

## Frequently Asked Questions

### What header do I need to include to format containers with fmt?

You must include **`#include <fmt/ranges.h>`** in addition to the standard `<fmt/format.h>`. The ranges header provides the `formatter` specializations and type detection mechanisms required to handle `std::vector`, `std::map`, and other iterable types.

### How do I format a map without curly braces?

Apply the **`n`** format specifier to suppress outer delimiters:

```cpp
std::map<std::string, int> m{{"x", 1}};
fmt::print("{:n}", m);  // Outputs: x: 1

```

This specifier works on any range type supported by [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h).

### Can I format C-style arrays using fmt ranges?

Yes. The ADL-based detection mechanism in `detail::has_const_begin_end` falls back to `range_begin` and `range_end` functions that handle raw C arrays. You can pass arrays directly to `fmt::print` without wrapping them in container objects.

### What is the difference between string and debug_string format kinds?

The **string** format kind treats character ranges as plain text without quotes, while **debug_string** (triggered by the `?` specifier or debug mode) escapes special characters and surrounds the output with quotes. This corresponds to the `range_format::string` and `range_format::debug_string` classifications in lines 252-260 of [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h).