Clang 17 has recently been released. It includes the new modernize-use-std-print check, which is the culmination of work I started two years ago. In short, it turns lines like:
fprintf(stderr, "The %s is %3d\n", description.c_str(), value);into:
std::println(stderr, "The {} is {:3}", description, value);The full documentation for the check is available on the clang-tidy
web site, but here are some examples. Given a file named
example.cpp containing:
#include <cstdio>
#include <string>
void print_row(int quantity, const std::string &name, double mass, const std::string &description)
{
printf("| %3d | %10s | %6.3fkg | %-30s |\n", quantity, name.c_str(), mass, description.c_str());
}
int main()
{
print_row(4, "carrots", 0.25, "Long orange things");
print_row(9, "sprouts", 0.1, "Roundish green things");
print_row(60, "peas", 0.15, "Round green things");
print_row(10, "beans", 0.05, "Long green things");
}then running
clang-tidy '-checks=-*,modernize-use-std-print' -fix example.cpp -- -std=c++23will result in the file being rewritten to:
#include <cstdio>
#include <print>
#include <string>
void print_row(int quantity, const std::string &name, double mass, const std::string &description)
{
std::println("| {:3} | {:>10} | {:6.3f}kg | {:30} |", quantity, name, mass, description);
}
int main()
{
print_row(4, "carrots", 0.25, "Long orange things");
print_row(9, "sprouts", 0.1, "Roundish green things");
print_row(60, "peas", 0.15, "Round green things");
print_row(10, "beans", 0.05, "Long green things");
}The check has done several things:
- The
<print>header is now included. (The inclusion of<cstdio>has not been removed since it may be required for other reasons.) - The
printfcall has been replaced withstd::println. - The
\nhas been removed from the end of the format string sincestd::printlnwill add a newline automatically. - The format string has been rewritten to use the new C++ format language.
- The now-unnecessary calls to
std::string::c_str()have been removed.
Despite all this, the output is the same.
Of course, most of us aren’t lucky enough to be using C++23 yet.
Helpfully, the author of the original std::print and
std::format feature proposal has the excellent {fmt} library which can be
used with earlier C++ versions (though sometimes with less
functionality.) The check can be customised to use calls to the {fmt}
equivalents instead. To make this easier, this time we’re going to
create a .clang-tidy configuration file in the same
directory as example.cpp since passing options on the
command line is rather unwieldy:
Checks: -*, modernize-use-std-print
CheckOptions:
modernize-use-std-print.PrintHeader: '<fmt/core.h>'
modernize-use-std-print.ReplacementPrintFunction: 'fmt::print'
modernize-use-std-print.ReplacementPrintlnFunction: 'fmt::println'Now, we no longer need to enable C++23 when running clang-tidy, and it will automatically pick up the configuration:
clang-tidy -fix example.cpp --results in:
#include <fmt/core.h>
#include <cstdio>
#include <string>
void print_row(int quantity, const std::string &name, double mass, const std::string &description)
{
fmt::println("| {:3} | {:>10} | {:6.3f}kg | {:30} |", quantity, name, mass, description);
}
int main()
{
print_row(4, "carrots", 0.25, "Long orange things");
print_row(9, "sprouts", 0.1, "Roundish green things");
print_row(60, "peas", 0.15, "Round green things");
print_row(10, "beans", 0.05, "Long green things");
}This is the same as before, except for the header inclusion and the
use of fmt::println rather than
std::println.
The check will only convert calls if it thinks that it can do so
without changing the behaviour. However, in order to enforce this it’s
necessary to enable the StrictMode feature. This ought to
only affect corner cases, where you may actually want the change in
behaviour anyway. Consider this code:
int count = -42;
printf("%u\n", count);On the majority of modern machines this will print
4294967254 but either count being
int or using %u may have been unintentional as
GCC’s -Wformat doesn’t complain about this. Running:
clang-tidy '-checks=-*,modernize-use-std-print' -fix example.cpp -- -std=c++23will yield:
int count = -42;
std::println("{}", count);which will always print -42.
If you really want the same output as before then it is necessary to
enable StrictMode with something like:
clang-tidy -config="{CheckOptions: [{key: StrictMode, value: true}]}" '-checks=-*,modernize-use-std-print' -fix example.cpp -- -std=c++23(or the equivalent in the .clang-tidy file) which
yields:
int i = -42;
std::println("{}", static_cast<unsigned int>(i));When appropriately configured, the check is also capable of
converting from fmt::printf and absl::PrintF.
When doing so, no casts are added by StrictMode since those
functions print based on the real type rather than the format
string.
The implementation of the check in Clang v17.0.1 unfortunately
contains a bug when
supplying field widths and precision in arguments. The check will
generate corrupt output (that probably won’t compile) when arguments
need to be reordered in combination with the addition of cases (for
StrictMode) or c_str() removal. For
example:
std::string s{"world"};
printf("%*s\n", 20, s.c_str());ought to be converted to:
std::string s{"world"};
std::println("{:>{}}", s, 20);This bug has been fixed on the main branch and that fix ought to be present in Clang v18.0.0 when it is released.