How to Migrate Existing printf/fprintf Code to fmt::print Using clang-tidy
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 and 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
constexprand that argument types are supported by fmt'sprintfparser ininclude/fmt/printf.h. - Rewrite: It constructs
fmt::printorfmt::fprintfcalls using the same format string and arguments, preservingstdoutorstderrstream targets. - Emit fix-it: clang-tidy produces a patch that can be applied automatically via the
--fixoption.
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 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:
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:
-
Generate a compile commands database if you haven't already. CMake users can pass
-DCMAKE_EXPORT_COMPILE_COMMANDS=ONto createcompile_commands.jsonin the build directory. -
Run clang-tidy with automatic fixing:
clang-tidy -p build <source-files> --fixThe
-p buildflag points to the directory containing your compile commands database. Replace<source-files>with specific paths or use scripts to batch process your codebase. -
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.
-
Remove obsolete headers. After successful migration, delete unnecessary includes of
<cstdio>or<stdio.h>wherefmt::printfrominclude/fmt/std.hreplaces all previous usage. -
Optional: Modernize to C++-style API. The check converts to
fmt::printandfmt::fprintf. To adopt the newer C++-style API usingfmt::formatwith 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 and include/fmt/printf.h:
Simple stdout output:
// 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:
// 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:
// 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
constexprformat strings. Calls likeprintf(dynamic_format, args)remain unchanged and must be manually converted tofmt::formatorfmt::vprint. - Custom printf extensions: Platform-specific format specifiers not recognized by fmt's parser in
include/fmt/printf.hwill not be transformed automatically. - Return value dependencies: Code relying on
printf's return value (character count) may need adjustment sincefmt::printreturnsvoid, whilefmt::fprintfmaintains return value compatibility.
Summary
- The
modernize-use-std-printcheck in clang-tidy v18+ automates migration fromprintf/fprintftofmt::print/fmt::fprintf. - Configuration requires enabling the check in
.clang-tidyand running with--fixagainst a compile commands database. - fmt provides compatible implementations in
include/fmt/printf.h(parser) andinclude/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, 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.
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.
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. The compatibility layer that parses printf-style format strings used during the migration is implemented in include/fmt/printf.h. The repository's own .clang-tidy file demonstrates the recommended configuration for enabling these transformations.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →