Saturday, 21 September 2024

New modernize-use-std-format clang-tidy check and modernize-use-std-print improvements in Clang 19

clang-tidy-modernize-use-std-format

Clang 19 has recently been released. It includes my new modernize-use-std-format check and fixes a bug in the modernize-use-std-print check that I added in Clang 17. (I also fixed another bug in the intervening Clang 18 release.)

The new modernise-use-std-format check by default turns calls to absl::StrFormat into calls to std::format. For example, it turns:

return absl::StrFormat("The %s is %d", description.c_str(), value);

into

return std::format("The {} is {}", description, value);

The full documentation for the check is available on the clang-tidy web site.

Although the default behaviour of the check is to replace calls to absl::StrFormat, many codebases have their own “wrap snprintf to return a std::string” function. Ours was called strprintf.

Given a file named example.cpp containing:

#include <string>

extern std::string strprintf(const char *format, ...);

std::string format_row(int quantity, const std::string &name, double mass, const std::string &description)
{
  return strprintf("| %3d | %10s | %6.3fkg | %-30s |\n", quantity, name.c_str(), mass, description.c_str());
}

int main()
{
  puts(format_row(4, "carrots", 0.25, "Long orange things").c_str());
  puts(format_row(9, "sprouts", 0.1, "Roundish green things").c_str());
}

then running

clang-tidy '-checks=-*,modernize-use-std-format' \
    -config="{CheckOptions: { modernize-use-std-format.StrFormatLikeFunctions: '::strprintf;' }}" \
    -fix example.cpp -- -std=c++20

will result in the file being rewritten to:

#include <format>
#include <string>                                                                                              'format' file not found [pp_file_not_found]

extern std::string strprintf(const char *format, ...);

std::string format_row(int quantity, const std::string &name, double mass, const std::string &description)
{
  return std::format("| {:3} | {:>10} | {:6.3f}kg | {:30} |\n", quantity, name, mass, description);
}

int main()
{
  puts(format_row(4, "carrots", 0.25, "Long orange things").c_str());
  puts(format_row(9, "sprouts", 0.1, "Roundish green things").c_str());
}

The check has done several things:

  1. The <format> header is now included. (The declaration of strprintf has not been removed since it may still be used in ways that could not be converted.)
  2. The strprintf call has been replaced with std::format.
  3. The format string has been rewritten to use the new C++ format language.
  4. The now-unnecessary calls to std::string::c_str() for name and description have been removed.

If you aren’t able to use C++20 yet then the check can be told to use fmt::format from the {fmt} library instead with something like:

clang-tidy '-checks=-*,modernize-use-std-format' \
    -config="{CheckOptions: { \
      modernize-use-std-format.StrFormatLikeFunctions: '::strprintf;', \
      modernize-use-std-format.ReplacementFormatFunction: 'fmt::format', \
      modernize-use-std-format.FormatHeader: '<fmt/core.h>' }}" \
    -fix example.cpp

You can play with the modernize-use-std-format and modernize-use-std-print checks using the excellent Compiler Explorer.

Thanks to everyone who reviewed and made suggestions for the new check and bug fixes. I have several further improvements to these checks in development that I hope will land in time for Clang 20.

No comments: