# How to Format C++ Ranges and Tuples with fmtlib: A Complete Guide

> Format C++ ranges and tuples easily with fmtlib. Learn how to include <fmt/ranges.h> and leverage compile-time traits for seamless formatting in this complete guide.

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

---

**fmtlib automatically formats any range or tuple-like type when you include `<fmt/ranges.h>`, using compile-time traits `fmt::is_range<T>` and `fmt::is_tuple_like<T>` to select dedicated formatter specializations.**

The {fmt} library provides first-class support for formatting C++ ranges and tuples without requiring manual iteration or string concatenation. By including [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h), you enable automatic detection and formatting of standard containers, arrays, pairs, and user-defined types that expose `begin`/`end` or `std::tuple_size`. This capability is implemented through specialized `formatter` templates that handle bracket delimiters, separators, and nested element formatting.

## Understanding fmtlib Range and Tuple Detection

The formatting engine distinguishes between ranges and tuple-like types using compile-time traits defined in [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h).

### Detecting Tuple-Like Types

The library identifies tuple-like structures through the presence of `std::tuple_size<T>::value`. The internal predicate `detail::is_tuple_like_` performs this check, which covers `std::tuple`, `std::pair`, `std::array`, and user-defined tuple-compatible structs. The public alias `fmt::is_tuple_like<T>` (lines 89-93) forwards to this implementation, enabling the selection of a dedicated formatter when every element is itself formattable (`fmt::is_tuple_formattable<T>`).

### Detecting Range Types

A type qualifies as a range when `detail::range_begin` and `detail::range_end` can be invoked successfully. This includes C-arrays, containers with member functions, or types found through argument-dependent lookup. The trait `fmt::is_range<T>` (lines 62-68) combines these checks while excluding optional-like types and objects that expose `to_string_view` overloads, preventing ambiguity with string formatting.

## Formatting Tuple-Like Types

When `fmt::is_tuple_like<T>` evaluates to true, the library instantiates a specialized `formatter` defined around lines 3000-3030 in [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h).

### The Tuple Formatter Specialization

The formatter constructs a compile-time array of element formatters using `detail::tuple::get_formatters`. During parsing, `parse()` forwards the format specification to each element formatter via `detail::parse_empty_specs`. The actual `format()` operation writes the configured opening bracket, iterates through elements using `detail::for_each2`, inserts the separator between values, and appends the closing bracket through the `write_body` method (lines 3015-3023).

```cpp
auto tup = std::make_tuple(42, 3.14, "abc");
fmt::print("Default: {}\n", tup);                 // (42, 3.14, abc)
fmt::print("Custom brackets: {:#}\n", tup);      // {42, 3.14, abc}
fmt::print("Custom sep: {:;}\n", tup);           // (42; 3.14; abc)

```

### Customizing Tuple Output

You can modify the default parentheses and comma separator at runtime using `set_brackets()` and `set_separator()` (lines 30-34). These methods allow dynamic reconfiguration of the formatter instance when you need different delimiters for specific output contexts.

## Formatting Range Types

For ranges where the element type satisfies formattability requirements, the library provides a generic `formatter` specialization located at lines 3600-3660.

### The Range Formatter Specialization

The range formatter stores an underlying element formatter of type `detail::range_formatter_type`. It iterates over the range in `write_body` (lines 4990-5015), formatting each element and inserting the configured separator, then surrounds the result with square brackets by default.

### Range Format Specifiers

The `parse()` method (lines 4440-4490) recognizes several format options:
- **`n`** — Suppresses brackets entirely, outputting only the comma-separated elements
- **`s`** — Applies string-style quoting to the output
- **`?`** — Enables debug output formatting

```cpp
std::vector<int> vec = {1, 2, 3};
fmt::print("Default: {}\n", vec);                // [1, 2, 3]
fmt::print("No brackets: {:n}\n", vec);          // 1, 2, 3
fmt::print("Debug string: {:?s}\n", vec);        // "1, 2, 3"

```

## Using fmt::join for Custom Separators

For both ranges and tuple-like objects, the `fmt::join` view (lines 8420-8665) formats elements with a custom separator without constructing intermediate strings or requiring bracket delimiters. This is more efficient than creating temporary strings when you only need flat concatenation.

```cpp
fmt::print("Join view: {}\n", fmt::join(vec, " | ")); // 1 | 2 | 3
fmt::print("Join tuple: {}\n", fmt::join(tup, "-"));   // 42-3.14-abc

```

The `fmt::join` function works with C-arrays, standard containers, and tuple-like types, accepting any formattable range as its first argument.

## Summary

- **Include `<fmt/ranges.h>`** to enable automatic formatting of ranges and tuples, as documented in the repository README (line 129).
- **Tuple detection** relies on `std::tuple_size` via `detail::is_tuple_like_`, while **range detection** uses `detail::range_begin`/`range_end` through the `fmt::is_range<T>` trait.
- **Default formatting** uses parentheses `()` for tuples and square brackets `[]` for ranges, both with `", "` as the element separator.
- **Runtime customization** allows changing brackets and separators via `set_brackets()` and `set_separator()` on the formatter instance.
- **Format specifiers** provide control over range output: `{:n}` removes brackets, `{:s}` adds quoting, and `{:?}` provides debug formatting.
- **Use `fmt::join`** when you need custom separators without brackets for either ranges or tuples.

## Frequently Asked Questions

### Why do I get a compilation error when trying to format a vector?

You must include [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h) in addition to the standard [`fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/fmt/format.h). According to the fmtlib source code, range formatting is provided as an extension module and is not available in the core formatting header. The README explicitly mentions this requirement at line 129.

### How does fmtlib distinguish between a range and a tuple?

The library uses separate compile-time traits: `fmt::is_tuple_like<T>` checks for `std::tuple_size<T>::value` (lines 89-93), while `fmt::is_range<T>` checks for valid `begin`/`end` expressions via `detail::range_begin` and `detail::range_end` (lines 62-68). A type can theoretically satisfy both, but tuple formatting takes precedence when `std::tuple_size` is present.

### Can I format nested containers like vector of tuples?

Yes. The formatters in [`include/fmt/ranges.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ranges.h) recursively check formattability of element types. The tuple formatter's `detail::tuple::get_formatters` constructs formatters for each element, while the range formatter uses `detail::range_formatter_type` for its elements. Both mechanisms support arbitrary nesting depths as long as all constituent types are formattable.

### What is the performance difference between `fmt::join` and standard range formatting?

`fmt::join` (lines 8420-8665) avoids constructing the opening and closing brackets and skips the `write_body` overhead of the standard formatter. It directly iterates the range or tuple elements, inserting only the specified separator. This makes it more efficient when you need simple delimited output without structural brackets, as it eliminates the branching and bracket-tracking logic found in the standard `write_body` implementations (lines 3015-3023 and 4990-5015).