# How to Use fmtlib for Formatted Output to Files

> Learn to write high-performance formatted output to files using fmtlib's ostream and output_file functions. Benefit from buffered I/O and compile-time format checking for robust code.

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

---

**The {fmt} library provides high-performance file output through `fmt::ostream`, created via `fmt::output_file()`, offering buffered I/O with compile-time format checking.**

The fmtlib/fmt repository delivers a modern C++ formatting library that extends beyond console output to efficient file operations. Unlike standard iostreams, fmtlib provides a thin, zero-overhead abstraction over POSIX file descriptors and Windows handles, combining the ergonomics of Python-style format strings with system-level performance. This guide explores the file I/O capabilities implemented in [`include/fmt/os.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h) and demonstrates production-ready patterns for formatted file output.

## Creating File Streams with fmt::output_file

The entry point for file operations is **`fmt::output_file`**, a factory function defined in [[`include/fmt/os.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h)](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h#L99-L115) that opens a file for writing and returns a `fmt::ostream` object.

By default, `output_file` creates the file if missing and truncates existing content. The function signature accepts optional open flags and buffer tuning parameters:

```cpp
#include <fmt/os.h>

int main() {
    // Opens "log.txt", truncates if exists, creates if missing
    auto out = fmt::output_file("log.txt");
    out.print("System initialized at {}\n", "2024-01-01");
} // Destructor flushes buffer and closes file descriptor automatically

```

The returned **`fmt::ostream`** object (declared at lines 59-85 in [`os.h`](https://github.com/fmtlib/fmt/blob/main/os.h)) inherits from `fmt::detail::buffer<char>` and encapsulates a `fmt::file` member, providing automatic buffer management and RAII-based resource cleanup.

## Writing Formatted Data Using ostream::print

Once initialized, `fmt::ostream` exposes the **`print`** method (implemented at lines 92-98 in [`include/fmt/os.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h)) which accepts compile-time checked format strings via `format_string<T...>`.

This method formats arguments directly into the internal buffer and flushes to disk when the buffer fills or the object destructs:

```cpp
#include <fmt/os.h>

int main() {
    auto out = fmt::output_file("data.txt");
    
    // Compile-time format checking prevents type mismatches
    out.print("User {} logged in {} times\n", "alice", 42);
    out.print("Hex value: {:#x}, Binary: {:08b}\n", 255, 128);
}

```

The `print` implementation leverages the same formatting engine as `fmt::format`, ensuring consistent behavior across console and file output while eliminating runtime format string parsing overhead.

## Understanding the Buffered I/O Architecture

According to the fmtlib source code, `fmt::ostream` implements an efficient **buffered I/O strategy** that accumulates writes in user-space memory before issuing a single system call via `file_.write()`. This design achieves up to approximately nine times the throughput of standard `fprintf` implementations.

The buffer size is configurable during stream creation using the `buffer_size` parameter:

```cpp
#include <fmt/os.h>

int main() {
    // Append mode with 64KB buffer for high-throughput logging
    auto out = fmt::output_file(
        "append.txt",
        fmt::file::APPEND,
        fmt::buffer_size = 64 * 1024
    );
    
    for (int i = 0; i < 1000000; ++i) {
        out.print("Event {}: timestamp={}\n", i, i * 0.001);
    }
}

```

For low-level operations requiring direct file descriptor access, the library exposes **`fmt::file`** (declared at lines 24-34 in [`os.h`](https://github.com/fmtlib/fmt/blob/main/os.h)), a wrapper around POSIX file descriptors or Windows handles:

```cpp
#include <fmt/os.h>

int main() {
    // Binary write using low-level file API
    fmt::file f("binary.bin",
                fmt::file::WRONLY | fmt::file::CREATE | fmt::file::TRUNC);
    
    const char raw[] = {0x01, 0x02, 0x03, 0x04};
    f.write(raw, sizeof(raw));
    f.close();
}

```

## Advanced File Operations and Error Handling

The **`fmt::buffered_file`** helper class (lines 66-78 in [`include/fmt/os.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h)) provides compatibility with legacy C APIs requiring `FILE*` pointers, while `fmt::file` supports operations like `dup()` for descriptor duplication and explicit `close()` control.

When opening files, combine flags from the `fmt::file` namespace to control behavior:

- **`fmt::file::APPEND`**: Write data to the end of file
- **`fmt::file::WRONLY`**: Open for writing only
- **`fmt::file::CREATE`**: Create file if it does not exist
- **`fmt::file::TRUNC`**: Truncate existing content

Explicit buffer flushing and file closure are available through `flush()` and `close()` methods, though RAII ensures resources release properly when `fmt::ostream` exits scope.

## Summary

- **Use `fmt::output_file()`** to create buffered file streams with customizable open flags and buffer sizes, defined in [`include/fmt/os.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h) lines 99-115.
- **Call `ostream.print()`** with type-safe format strings validated at compile-time via `format_string<T...>`, implemented at lines 92-98.
- **Leverage buffered I/O** for up to 9× performance improvement over standard I/O by buffering writes in `detail::buffer<char>` before system calls.
- **Access low-level APIs** through `fmt::file` for binary operations or descriptor manipulation (lines 24-34).
- **Rely on RAII** for automatic resource cleanup, or manually control resources with `flush()` and `close()`.

## Frequently Asked Questions

### How do I append to an existing file instead of truncating it?

Pass the `fmt::file::APPEND` flag as the second argument to `fmt::output_file()`. You can combine this with a custom buffer size for high-frequency logging scenarios. The factory function at lines 99-115 in [`include/fmt/os.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h) processes these flags directly through the underlying `fmt::file` constructor.

### Is fmtlib file output thread-safe?

The `fmt::ostream` class does not provide internal synchronization for concurrent writes. According to the implementation in [`include/fmt/os.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h), simultaneous access to the same `ostream` instance from multiple threads requires external locking. However, separate `ostream` instances managing different file descriptors can operate concurrently without interference.

### How does fmtlib file performance compare to std::ofstream?

The fmtlib implementation achieves significantly higher throughput than `std::ofstream` by buffering data in user-space and issuing fewer system calls. The source code leverages `fmt::detail::buffer<char>` accumulation before calling `file_.write()`, resulting in approximately nine times the performance of `fprintf` and standard stream implementations for formatted output workloads.

### Can I use fmtlib with existing FILE* pointers or raw file descriptors?

Yes. While `fmt::output_file` creates new file descriptors, the **`fmt::buffered_file`** class (lines 66-78 in [`include/fmt/os.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h)) wraps existing `FILE*` handles, and `fmt::file` supports construction from raw POSIX file descriptors via the `fd` parameter. This allows integration with legacy C APIs or pre-opened file handles while maintaining access to fmtlib's formatting capabilities.