# How to Use the C-style printf API with fmtlib

> Learn how to use the C-style printf API with fmtlib. Discover fmtlib's type-safe replacement for printf and its modern C++ formatting engine.

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

---

**fmtlib provides a type-safe, drop-in replacement for C-style `printf` functions via the `<fmt/printf.h>` header, implementing familiar formatting syntax on top of its modern C++ formatting engine.**

The `fmt` library (available at **fmtlib/fmt**) delivers a fully-featured `printf`-compatible API that allows you to migrate legacy C formatting code without sacrificing performance or safety. Unlike the standard C library functions, this implementation parses format strings through [`include/fmt/printf.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h) and routes them through the same optimized formatting core used by `fmt::format`, ensuring memory safety while preserving traditional specifier syntax.

## Core Architecture

The `printf` implementation in fmtlib consists of several coordinated components that transform C-style format strings into type-safe operations.

### Argument Collection with make_printf_args

The entry point for argument handling is **`fmt::make_printf_args`**, defined at lines 71-75 in [`include/fmt/printf.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h). This function template constructs a `basic_format_args` object that holds type-erased references to user-supplied arguments, bridging the variadic template interface with the internal formatting machinery.

```cpp
template <typename Char = char, typename... T>
inline auto make_printf_args(T&... args)
    -> decltype(fmt::make_format_args<basic_printf_context<Char>>(args...)) {
  return fmt::make_format_args<basic_printf_context<Char>>(args...);
}

```

### The Printf Context

The **`basic_printf_context`** struct (lines 21-48 in [`printf.h`](https://github.com/fmtlib/fmt/blob/main/printf.h)) serves as the execution environment for formatting operations. It stores an output appender and the argument list, acting as the bridge between the `printf` parser and fmtlib's generic formatting infrastructure.

```cpp
template <typename Char> class basic_printf_context {
  // …
  basic_appender<Char> out_;
  basic_format_args<basic_printf_context> args_;
};

```

### Format String Parsing and Conversion

The heavy lifting occurs in **`detail::vprintf`** (lines 405-410), which iterates over the format string to extract flags, width, precision, and conversion specifiers. For each specifier, the parser invokes **`convert_arg`** (lines 61-68) to adapt the stored `basic_format_arg` to the exact type required (e.g., `short`, `long long`, `char`).

After conversion, **`printf_arg_formatter`** (lines 30-39) visits the argument and calls `detail::write` to emit the formatted output, reusing the high-performance routines that power `fmt::format`.

## Public API Functions

The public interface consists of thin wrappers around the internal `vprintf` implementation, all declared around lines 44-50 in [`include/fmt/printf.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h):

- **`fmt::printf`** – Writes formatted output directly to `stdout`.
- **`fmt::fprintf`** – Writes to a user-provided `FILE*` stream.
- **`fmt::sprintf`** – Returns the formatted result as a `std::string`.
- **`fmt::vsprintf`** and **`fmt::vfprintf`** – Accept pre-built `printf_args` objects for cases where you need to forward argument packs.

These functions automatically invoke `make_printf_args` internally, providing seamless integration with the type-safe formatting core.

## Practical Usage Examples

The following examples demonstrate the breadth of the `printf` API:

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

// 1. Simple printf to stdout
fmt::printf("Hello %s, the answer is %d\n", "world", 42);

// 2. sprintf – get a std::string
std::string msg = fmt::sprintf("Pi ≈ %.3f", 3.1415926);
fmt::printf("%s\n", msg);

// 3. fprintf – write to a FILE*
FILE* out = fopen("log.txt", "w");
fmt::fprintf(out, "Error %d: %s\n", 404, "Not Found");
fclose(out);

// 4. Using positional arguments (the same as %1$, %2$, …)
fmt::printf("%2$s %1$d %2$s\n", 10, "repeat");

// 5. Width/precision with * (dynamic)
int w = 8, p = 3;
fmt::printf("%*.*f\n", w, p, 1.234567);   // prints "   1.235"

```

## Key Implementation Files

- **[`include/fmt/printf.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h)** – Contains the complete `printf`-style API including `make_printf_args`, `basic_printf_context`, `detail::vprintf`, and the public `printf`/`fprintf`/`sprintf` functions.
- **[`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h)** – Provides core formatting utilities like `detail::write` and buffer management used by the `printf` implementation.
- **[`include/fmt/core.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h)** – Defines fundamental types such as `basic_appender` and `basic_format_args` that the `printf` context builds upon.
- **[`include/fmt/args.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/args.h)** – Houses the `basic_format_arg` definition and type-dispatch machinery leveraged during argument conversion.

## Summary

- **fmtlib** implements C-style `printf` through [`include/fmt/printf.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h), offering a direct replacement for standard C functions.
- **`make_printf_args`** (lines 71-75) and **`basic_printf_context`** (lines 21-48) provide the type-safe foundation for argument handling.
- The parsing engine in **`detail::vprintf`** (lines 405-410) supports full POSIX extensions including positional arguments and dynamic width/precision.
- Public functions **`fmt::printf`**, **`fmt::fprintf`**, and **`fmt::sprintf`** (lines 44-50) wrap the core engine for drop-in compatibility.
- Unlike C `printf`, this implementation prevents undefined behavior through type-checked argument conversion in **`convert_arg`** (lines 61-68).

## Frequently Asked Questions

### Is fmtlib's printf API type-safe?

Yes. While it accepts traditional C format strings, fmtlib stores arguments in a `basic_format_args` container and validates types through **`convert_arg`** (lines 61-68). This eliminates undefined behavior from mismatched specifiers, such as passing a `float` to a `%d` format, which would corrupt memory in standard C `printf`.

### Does fmtlib support POSIX printf extensions?

Yes. The parser in `detail::vprintf` (lines 405-410) fully supports POSIX positional arguments using the `%1$`, `%2$` syntax and dynamic width/precision with the `*` character. You can safely use expressions like `fmt::printf("%2$s %1$d", 42, "order")` to reorder arguments dynamically.

### How do I choose between fmt::printf and fmt::format?

Use **`fmt::printf`** when migrating legacy codebases or interfacing with APIs that require C-style format strings. Use **`fmt::format`** for new development to benefit from Python-style format strings, compile-time checking, and improved performance through compile-time format string parsing.

### What are the differences between fmt::sprintf and the C standard sprintf?

**`fmt::sprintf`** returns a `std::string` directly rather than writing to a fixed-size buffer, eliminating buffer overflow vulnerabilities. It also leverages the type-safe conversion pipeline in **`printf_arg_formatter`** (lines 30-39), ensuring that arguments match their format specifiers before formatting occurs.