# How to Migrate Existing printf/fprintf Code to fmt::print Using clang-tidy

> Easily migrate printf fprintf to fmt::print with clang-tidy. Automate C-style I/O conversion using the modernize-use-std-print check for safer, modern code. Fix your C++ I/O today.

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

---

**You can automate the migration of legacy `printf` and `fprintf` calls to type-safe `fmt::print` equivalents by enabling the `modernize-use-std-print` clang-tidy check and running it with the `--fix` flag to automatically rewrite C-style I/O calls to their fmt library counterparts.**

The **fmt** library provides a high-performance, type-safe alternative to C-style I/O functions. To migrate existing printf fprintf code to fmt::print using clang-tidy efficiently across large codebases, you leverage the automated refactoring capabilities of the `modernize-use-std-print` check, which parses abstract syntax trees and emits fix-it hints that transform calls to use the **fmt** library's compatible API defined in [`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h) and [`include/fmt/printf.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h).

## How the modernize-use-std-print Check Works

The `modernize-use-std-print` check in clang-tidy locates calls to `printf`, `fprintf`, `vprintf`, and related functions, then generates replacement code using `fmt::print` or `fmt::fprintf` while preserving the original format strings.

### The Transformation Algorithm

According to the fmtlib/fmt source code, the check performs the following steps:

- **Parse**: clang builds an AST and identifies call expressions resolving to `printf`, `fprintf`, `vprintf`, or similar functions.
- **Validate**: It verifies the format string is a literal or `constexpr` and that argument types are supported by **fmt**'s `printf` parser in [`include/fmt/printf.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h).
- **Rewrite**: It constructs `fmt::print` or `fmt::fprintf` calls using the same format string and arguments, preserving `stdout` or `stderr` stream targets.
- **Emit fix-it**: clang-tidy produces a patch that can be applied automatically via the `--fix` option.

This transformation respects format-string semantics because **fmt** implements a drop-in compatible `printf` parser, enabling safe automated migration before optionally adopting the modern C++-style API.

## Configuring clang-tidy for fmt Migration

### Prerequisites

You need clang-tidy version 18 or later, which includes the `modernize-use-std-print` check. The fmt repository's [`doc/get-started.md`](https://github.com/fmtlib/fmt/blob/main/doc/get-started.md) notes that clang-tidy v18 provides the necessary functionality for this migration path.

### Setting Up .clang-tidy

Create or modify a `.clang-tidy` file in your repository root to enable the specific check:

```yaml
Checks: '-*,modernize-use-std-print'
WarningsAsErrors: ''
HeaderFilterRegex: '.*'
FormatStyle:     file

```

This configuration disables all default checks and enables only the printf-to-fmt migration. The `HeaderFilterRegex` ensures headers are also processed, while `FormatStyle: file` respects your project's `.clang-format` settings.

## Step-by-Step Migration Process

Follow these steps to execute the automated migration:

1. **Generate a compile commands database** if you haven't already. CMake users can pass `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to create [`compile_commands.json`](https://github.com/fmtlib/fmt/blob/main/compile_commands.json) in the build directory.

2. **Run clang-tidy with automatic fixing**:

   ```bash
   clang-tidy -p build <source-files> --fix
   ```

   The `-p build` flag points to the directory containing your compile commands database. Replace `<source-files>` with specific paths or use scripts to batch process your codebase.

3. **Review unmodified calls**. clang-tidy will comment on cases it cannot safely transform, such as non-literal format strings constructed at runtime. Manually refactor these instances.

4. **Remove obsolete headers**. After successful migration, delete unnecessary includes of `<cstdio>` or `<stdio.h>` where `fmt::print` from [`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h) replaces all previous usage.

5. **Optional: Modernize to C++-style API**. The check converts to `fmt::print` and `fmt::fprintf`. To adopt the newer C++-style API using `fmt::format` with streams, perform additional manual replacements or write a custom clang-tidy check.

## Code Transformation Examples

The following examples demonstrate typical transformations performed by the check, referencing definitions in [`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h) and [`include/fmt/printf.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h):

**Simple stdout output:**

```cpp
// Before
printf("Result: %d\n", value);

// After clang-tidy transformation
fmt::print("Result: %d\n", value);  // Defined in include/fmt/std.h

```

**stderr output with fprintf:**

```cpp
// Before
fprintf(stderr, "Error: %s\n", msg);

// After clang-tidy transformation
fmt::fprintf(stderr, "Error: %s\n", msg);  // Defined in include/fmt/std.h

```

**Precision arguments:**

```cpp
// Before
printf("%.*s", (int)len, str);

// After clang-tidy transformation
fmt::print("%.*s", static_cast<int>(len), str);

```

All transformed functions utilize **fmt**'s type-safe formatting system while maintaining the original `printf` format string syntax for compatibility during the migration phase.

## Handling Edge Cases and Limitations

When you migrate existing printf fprintf code to fmt::print using clang-tidy, certain constructs require manual intervention:

- **Dynamic format strings**: The check only transforms literal or `constexpr` format strings. Calls like `printf(dynamic_format, args)` remain unchanged and must be manually converted to `fmt::format` or `fmt::vprint`.
- **Custom printf extensions**: Platform-specific format specifiers not recognized by **fmt**'s parser in [`include/fmt/printf.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h) will not be transformed automatically.
- **Return value dependencies**: Code relying on `printf`'s return value (character count) may need adjustment since `fmt::print` returns `void`, while `fmt::fprintf` maintains return value compatibility.

## Summary

- The `modernize-use-std-print` check in clang-tidy v18+ automates migration from `printf`/`fprintf` to `fmt::print`/`fmt::fprintf`.
- Configuration requires enabling the check in `.clang-tidy` and running with `--fix` against a compile commands database.
- **fmt** provides compatible implementations in [`include/fmt/printf.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h) (parser) and [`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h) (modern API).
- The transformation preserves format strings and stream targets while converting to type-safe equivalents.
- Manual cleanup is required for dynamic format strings and removal of obsolete `<cstdio>` headers.

## Frequently Asked Questions

### What versions of clang-tidy support the modernize-use-std-print check?

The check requires clang-tidy version 18 or later. As noted in the fmtlib/fmt repository's [`doc/get-started.md`](https://github.com/fmtlib/fmt/blob/main/doc/get-started.md), clang-tidy v18 introduced the ability to transform printf-style calls into their **fmt** library equivalents.

### Does the check handle dynamic format strings?

No. The `modernize-use-std-print` check only transforms calls where the format string is a literal or compile-time constant. Runtime-constructed format strings are skipped and must be manually migrated using `fmt::format` or `fmt::vprint` from [`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h).

### Can I migrate directly to fmt::format instead of fmt::print?

The standard check converts to `fmt::print` or `fmt::fprintf`. To use `fmt::format` (which returns a string rather than printing directly), you must either perform manual replacements after the initial migration or author a custom clang-tidy check that targets the `fmt::format` API defined in [`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h).

### Where are the fmt::print and fmt::fprintf definitions located?

According to the fmtlib/fmt source code, `fmt::print`, `fmt::fprintf`, and `fmt::println` are declared in [`include/fmt/std.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/std.h). The compatibility layer that parses printf-style format strings used during the migration is implemented in [`include/fmt/printf.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h). The repository's own `.clang-tidy` file demonstrates the recommended configuration for enabling these transformations.