How fmtlib Implements the Ostream Formatter Extension for operator<<

The fmt library enables formatting of any type supporting operator<< through a three-part mechanism in include/fmt/ostream.h: basic_ostream_formatter streams values into a temporary buffer, streamed_view acts as a type wrapper, and fmt::streamed() provides a user-facing API.

The {fmt} library offers flexible C++ formatting that extends beyond built-in types. When you need to format custom classes that already implement std::ostream insertion operators, fmtlib provides a specialized ostream formatter extension that bridges standard stream operations with the library's high-performance formatting pipeline.

Overview of the Ostream Formatter Extension

Unlike fmtlib's core formatters that write directly to output iterators, the ostream extension accommodates legacy codebases and third-party types that only expose operator<<. This extension lives entirely within include/fmt/ostream.h and leverages std::basic_ostream internally while presenting a modern fmt::format interface to users.

The Three-Part Implementation

The implementation consists of three coordinated components that transform streamed output into fmtlib's native buffer-based formatting system.

basic_ostream_formatter: The Core Engine

At the heart of the extension lies basic_ostream_formatter, a class template that constructs a temporary string by streaming values into a std::basic_ostream. According to the fmtlib source code in include/fmt/ostream.h, the formatter uses a basic_memory_buffer to capture output before transferring it to fmtlib's formatting pipeline:

template <typename Char>
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
  template <typename T, typename Context>
  auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
    auto buffer = basic_memory_buffer<Char>();
    auto&& formatbuf = detail::formatbuf<std::basic_streambuf<Char>>(buffer);
    auto&& output = std::basic_ostream<Char>(&formatbuf);
    output.imbue(std::.locale::classic());
    output << value;                         // ← uses the user‑provided operator<<
    output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
    return formatter<basic_string_view<Char>, Char>::format(
        {buffer.data(), buffer.size()}, ctx);
  }
};

Key technical details:

  • basic_memory_buffer<Char>: Allocates a stack-backed buffer to minimize heap allocations during streaming.
  • detail::formatbuf: Wraps the memory buffer in a std::basic_streambuf interface, allowing std::basic_ostream to write directly into fmtlib's memory management system.
  • Locale handling: The formatter explicitly imbues the classic "C" locale to ensure consistent behavior across platforms.
  • Exception masking: Sets exception flags to catch stream errors before they propagate into the formatting context.

streamed_view: The Type Wrapper

To distinguish between types that should use ostream formatting versus native fmtlib formatters, the library introduces detail::streamed_view<T>. The specialization of formatter for this wrapper connects the generic view to basic_ostream_formatter:

template <typename T, typename Char>
struct formatter<detail::streamed_view<T>, Char>
    : basic_ostream_formatter<Char> {
  template <typename Context>
  auto format(detail::streamed_view<T> view, Context& ctx) const
      -> decltype(ctx.out()) {
    return basic_ostream_formatter<Char>::format(view.value, ctx);
  }
};

This indirection allows fmtlib to treat ostream-formatted types as first-class citizens within the format string parsing system while maintaining compile-time type safety.

fmt::streamed: The User Interface

Users interact with this system through the fmt::streamed helper function, defined at lines 113-116 of include/fmt/ostream.h. This constexpr function creates the streamed_view wrapper without exposing implementation details:

template <typename T>
constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
  return {value};
}

Practical Usage Example

To format a custom type using its operator<<, include fmt/ostream.h and wrap your value with fmt::streamed():

#include <fmt/ostream.h>
#include <iostream>

struct Point {
  int x, y;
};

std::ostream& operator<<(std::ostream& os, const Point& p) {
  return os << '(' << p.x << ", " << p.y << ')';
}

int main() {
  Point pt{3, 7};
  
  // Use fmt::streamed to invoke operator<< based formatting
  std::string s = fmt::format("The point is {}", fmt::streamed(pt));
  // Result: "The point is (3, 7)"
  
  // Works with fmt::print to any ostream
  fmt::print(std::cout, "Point: {}\n", fmt::streamed(pt));
  // Prints: Point: (3, 7)
}

What happens at runtime:

  1. fmt::streamed(pt) constructs a detail::streamed_view<Point> containing a reference to pt.
  2. The format string parser selects formatter<detail::streamed_view<Point>, char> due to the wrapped type.
  3. The specialization delegates to basic_ostream_formatter<char>::format(), which creates a temporary std::ostream backed by fmtlib's memory buffer.
  4. operator<< streams into this buffer, and the resulting string view feeds back into the standard formatting pipeline.

Summary

  • basic_ostream_formatter in include/fmt/ostream.h provides the machinery to capture operator<< output into fmtlib memory buffers using detail::formatbuf.
  • detail::streamed_view<T> acts as a type tag that triggers ostream-based formatting through template specialization.
  • fmt::streamed() offers a clean, constexpr API for opting into ostream formatting within fmt::format calls.
  • The extension seamlessly integrates legacy stream-based types with modern fmtlib formatting without requiring custom formatter specializations.

Frequently Asked Questions

How do I format a type that only has operator<< with fmtlib?

Wrap your value with fmt::streamed() inside the format string. Ensure you include fmt/ostream.h to access the ostream formatter extension. This tells fmtlib to use the standard stream insertion operator rather than looking for a native formatter specialization.

Why does fmtlib use a memory buffer instead of stringstream?

The implementation uses basic_memory_buffer instead of std::stringstream to avoid the overhead of std::string allocations and to maintain compatibility with fmtlib's output iterator architecture. The detail::formatbuf adapter allows std::basic_ostream to write directly into fmtlib's optimized buffer pool, resulting in better performance than standard string streams.

Can I customize the locale when using the ostream formatter?

The basic_ostream_formatter explicitly imbues the classic "C" locale (std::locale::classic()) on the temporary stream object. If you need locale-specific formatting, you must implement a custom formatter specialization rather than relying on the ostream extension, as the current implementation forces classic locale behavior to ensure consistency across platforms.

Is there a performance penalty for using fmt::streamed versus a native formatter?

Yes, the ostream formatter extension incurs overhead compared to native fmtlib formatters. It requires constructing a std::basic_ostream object, allocating a basic_memory_buffer, and virtual function calls through the stream buffer interface. For hot paths, implementing a direct formatter<T> specialization that writes to ctx.out() avoids this indirection and provides significantly better performance.

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 →