How to Format Containers and Ranges Using fmtlib ranges.h

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 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 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 (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 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:

#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:

#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:

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:

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:

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.

#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
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 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:

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.

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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →