[clang-tools-extra] [clang-tidy][modernize-use-std-erase] Replace iterator erase-remove idiom with std::(erase/erase_if) (PR #193407)

Omar Hossam via cfe-commits cfe-commits at lists.llvm.org
Sun Apr 26 02:10:36 PDT 2026


https://github.com/moar55 updated https://github.com/llvm/llvm-project/pull/193407

>From 85bbb13ef79566f31c6784b2a3dbe613847049d6 Mon Sep 17 00:00:00 2001
From: Omar Ibrahim <moar.ahmed at gmail.com>
Date: Wed, 22 Apr 2026 06:48:56 +0200
Subject: [PATCH 1/5] Replace iterator version of erase-remove idiom with
 std::erase and std::erase_if

---
 .../clang-tidy/modernize/CMakeLists.txt       |   1 +
 .../modernize/ModernizeTidyModule.cpp         |   2 +
 .../clang-tidy/modernize/UseStdEraseCheck.cpp | 125 ++++++++++++++++++
 .../clang-tidy/modernize/UseStdEraseCheck.h   |  35 +++++
 clang-tools-extra/docs/ReleaseNotes.rst       |   5 +
 .../docs/clang-tidy/checks/list.rst           |   1 +
 .../checks/modernize/use-std-erase.rst        |  16 +++
 .../checkers/modernize/use-std-erase.cpp      | 122 +++++++++++++++++
 8 files changed, 307 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.h
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-erase.rst
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-erase.cpp

diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index 2c5c44db587fe..45cf3d1c7ad92 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -12,6 +12,7 @@ add_clang_library(clangTidyModernizeModule STATIC
   ConcatNestedNamespacesCheck.cpp
   DeprecatedHeadersCheck.cpp
   DeprecatedIosBaseAliasesCheck.cpp
+  UseStdEraseCheck.cpp
   IntegralLiteralExpressionMatcher.cpp
   LoopConvertCheck.cpp
   LoopConvertUtils.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index cc13da7535bcb..abd2f11a44e48 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -16,6 +16,7 @@
 #include "ConcatNestedNamespacesCheck.h"
 #include "DeprecatedHeadersCheck.h"
 #include "DeprecatedIosBaseAliasesCheck.h"
+#include "UseStdEraseCheck.h"
 #include "LoopConvertCheck.h"
 #include "MacroToEnumCheck.h"
 #include "MakeSharedCheck.h"
@@ -81,6 +82,7 @@ class ModernizeModule : public ClangTidyModule {
         "modernize-deprecated-headers");
     CheckFactories.registerCheck<DeprecatedIosBaseAliasesCheck>(
         "modernize-deprecated-ios-base-aliases");
+    CheckFactories.registerCheck<UseStdEraseCheck>("modernize-use-std-erase");
     CheckFactories.registerCheck<LoopConvertCheck>("modernize-loop-convert");
     CheckFactories.registerCheck<MacroToEnumCheck>("modernize-macro-to-enum");
     CheckFactories.registerCheck<MakeSharedCheck>("modernize-make-shared");
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.cpp
new file mode 100644
index 0000000000000..6e2b73054561e
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.cpp
@@ -0,0 +1,125 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseStdEraseCheck.h"
+#include "../utils/Matchers.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchersInternal.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Lex/Lexer.h"
+#include "llvm/ADT/StringRef.h"
+#include <initializer_list>
+#include <string_view>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+namespace {
+
+constexpr std::array<llvm::StringRef, 2> EraseEndMethodNames = {"end", "cend"};
+constexpr std::array<llvm::StringRef, 2> EraseEndFreeNames = {"end", "cend"};
+constexpr const char *EraseThis = "EraseThis";
+
+AST_MATCHER(Expr, hasSideEffects) {
+  return Node.HasSideEffects(Finder->getASTContext());
+}
+
+auto makeExprMatcher(
+    const ast_matchers::internal::Matcher<Expr> &ArgumentMatcher,
+    ArrayRef<StringRef> MethodNames, ArrayRef<StringRef> FreeNames) {
+  return expr(
+      anyOf(cxxMemberCallExpr(argumentCountIs(0),
+                              callee(cxxMethodDecl(hasAnyName(MethodNames))),
+                              on(ArgumentMatcher)),
+            callExpr(argumentCountIs(1), hasArgument(0, ArgumentMatcher),
+                     hasDeclaration(functionDecl(hasAnyName(FreeNames))))));
+}
+
+ast_matchers::internal::Matcher<Expr> makeMatcherPair() {
+  ast_matchers::internal::Matcher<CallExpr> ArgumentMatcher = allOf(
+      hasArgument(
+          0, makeExprMatcher(expr(unless(hasSideEffects())).bind(EraseThis),
+                             {"begin"}, {"::std::begin"})),
+      hasArgument(
+          1, makeExprMatcher(
+                 expr(matchers::isStatementIdenticalToBoundNode(EraseThis)),
+                 {"end"}, {"::std::end"})),
+      hasArgument(2, expr().bind("valueOrCond")));
+
+  return callExpr(callee(functionDecl(hasAnyName("remove", "remove_if"))),
+                  argumentCountIs(3), ArgumentMatcher)
+      .bind("remove");
+}
+
+} // namespace
+
+void UseStdEraseCheck::registerMatchers(MatchFinder *Finder) {
+  const auto IsCpp20EraseContainer = cxxRecordDecl(
+      hasAnyName("vector", "deque", "list", "forward_list", "basic_string"),
+      isInStdNamespace());
+
+  const auto EraseableContainerType = type(hasUnqualifiedDesugaredType(
+      tagType(hasDeclaration(IsCpp20EraseContainer))));
+
+  auto EraseEndCheck = makeExprMatcher(
+      expr(matchers::isStatementIdenticalToBoundNode(EraseThis)),
+      EraseEndMethodNames, EraseEndFreeNames);
+
+  Finder->addMatcher(
+      cxxMemberCallExpr(callee(cxxMethodDecl(hasName("erase"))),
+                        hasArgument(0, makeMatcherPair()),
+                        hasArgument(1, EraseEndCheck),
+                        on(anyOf(hasType(EraseableContainerType),
+                                 hasType(pointsTo(EraseableContainerType)))))
+          .bind("erase"),
+      this);
+}
+
+void UseStdEraseCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *EraseCall = Result.Nodes.getNodeAs<CXXMemberCallExpr>("erase");
+  const auto *RemoveCall = Result.Nodes.getNodeAs<CallExpr>("remove");
+  const auto *ContainerThis = Result.Nodes.getNodeAs<Expr>(EraseThis);
+  const auto *ValueOrCond = Result.Nodes.getNodeAs<Expr>("valueOrCond");
+
+  if (!EraseCall || !RemoveCall || !ContainerThis || !ValueOrCond)
+    return;
+
+  const CXXMethodDecl *EraseMethod = EraseCall->getMethodDecl();
+  if (!EraseMethod)
+    return;
+
+  const std::string RemoveFuncName =
+      RemoveCall->getDirectCallee()->getName().str();
+
+  const std::string ReplacementFreeFunc =
+      RemoveFuncName == "remove" ? "std::erase" : "std::erase_if";
+
+  std::string Replacement =
+      ReplacementFreeFunc + "(" +
+      Lexer::getSourceText(
+          CharSourceRange::getTokenRange(ContainerThis->getSourceRange()),
+          Result.Context->getSourceManager(), Result.Context->getLangOpts())
+          .str() +
+      ", " +
+      Lexer::getSourceText(
+          CharSourceRange::getTokenRange(ValueOrCond->getSourceRange()),
+          Result.Context->getSourceManager(), Result.Context->getLangOpts())
+          .str() +
+      ")";
+
+  diag(EraseCall->getExprLoc(),
+       "prefer %0 over the erase-" + RemoveFuncName + " idiom")
+      << ReplacementFreeFunc
+      << FixItHint::CreateReplacement(EraseCall->getSourceRange(), Replacement);
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.h
new file mode 100644
index 0000000000000..6a5023533ff89
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.h
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDERASECHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDERASECHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::modernize {
+
+/// Replace erase-remove idiom with C++20's std::erase and std::erase_if for
+/// improved readability.
+///
+class UseStdEraseCheck : public ClangTidyCheck {
+public:
+  UseStdEraseCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus20;
+  }
+  std::optional<TraversalKind> getCheckTraversalKind() const override {
+    return TK_IgnoreUnlessSpelledInSource;
+  }
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDERASECHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index f70bf9e9f9eb8..19eaf9faa3ab2 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -145,6 +145,11 @@ New checks
   ``llvm::to_vector(llvm::make_filter_range(...))`` that can be replaced with
   ``llvm::map_to_vector`` and ``llvm::filter_to_vector``.
 
+- New :doc:`modernize-use-std-erase
+  <clang-tidy/checks/modernize/use-std-erase>` check.
+
+  Replaces erase-remove idiom with C++20's' std::erase and std::erase_if for improved readability.
+
 - New :doc:`modernize-use-std-bit
   <clang-tidy/checks/modernize/use-std-bit>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 053ce6f0779d9..8571f8b7b0e20 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -327,6 +327,7 @@ Clang-Tidy Checks
    :doc:`modernize-use-scoped-lock <modernize/use-scoped-lock>`, "Yes"
    :doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "Yes"
    :doc:`modernize-use-std-bit <modernize/use-std-bit>`, "Yes"
+   :doc:`modernize-use-std-erase <modernize/use-std-erase>`, "Yes"
    :doc:`modernize-use-std-format <modernize/use-std-format>`, "Yes"
    :doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes"
    :doc:`modernize-use-std-print <modernize/use-std-print>`, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-erase.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-erase.rst
new file mode 100644
index 0000000000000..9bd3fb26035de
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-erase.rst
@@ -0,0 +1,16 @@
+.. title:: clang-tidy - modernize-use-std-erase
+
+modernize-use-std-erase
+=======================
+
+Replaces erase-remove idiom with C++20's' std::erase and std::erase_if 
+for improved readability.
+
+Covered scenarios:
+
+========================================================== ============================
+Expression                                                 Replacement
+---------------------------------------------------------- ----------------------------
+``v.erase(std::remove(v.begin(), v.end(), 5), v.end())``   ``std::erase(v, 5)``
+``l.erase(std::remove_if(v.begin(), v.end(), 5), isEven)`` ``std::erase_if(v, isEven)``
+========================================================== ============================
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-erase.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-erase.cpp
new file mode 100644
index 0000000000000..afce0c1c9d243
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-erase.cpp
@@ -0,0 +1,122 @@
+// RUN: %check_clang_tidy -std=c++20 %s modernize-use-std-erase %t
+
+namespace std {
+
+template <typename T>
+struct vector {
+  using iterator = T*;
+  using reverse_iterator = T*;
+  iterator begin();
+  iterator end();
+  reverse_iterator rbegin();
+  reverse_iterator rend();
+  iterator erase(iterator, iterator);
+};
+
+template <typename T>
+struct deque {
+  using iterator = T*;
+  iterator begin();
+  iterator end();
+  iterator erase(iterator, iterator);
+};
+
+template <typename T>
+struct list {
+  using iterator = T*;
+  iterator begin();
+  iterator end();
+  iterator erase(iterator, iterator);
+};
+
+template <typename T>
+struct basic_string {
+  using iterator = T*;
+  iterator begin();
+  iterator end();
+  iterator erase(iterator, iterator);
+};
+using string = basic_string<char>;
+
+template <class ForwardIt, class T>
+ForwardIt remove(ForwardIt first, ForwardIt last, const T& value);
+
+template <class ForwardIt, class UnaryPredicate>
+ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPredicate p);
+
+// Dummy implementation
+template <class ForwardIt, class UnaryPredicate>
+ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPredicate p) {
+  return first;
+}
+
+} // namespace std
+
+// Custom container - should be ignored
+template <typename T>
+struct MyContainer {
+  using iterator = T*;
+  iterator begin();
+  iterator end();
+  iterator erase(iterator, iterator);
+};
+
+void test_standard_remove_idiom() {
+  std::vector<int> v;
+  v.erase(std::remove(v.begin(), v.end(), 42), v.end());
+  // CHECK-MESSAGES: {{.*}}: warning: prefer std::erase over the erase-remove idiom [modernize-use-std-erase]
+  // CHECK-FIXES: std::erase(v, 42);
+
+  std::deque<int> d;
+  d.erase(std::remove(d.begin(), d.end(), 42), d.end());
+  // CHECK-MESSAGES: {{.*}}: warning: prefer std::erase over the erase-remove idiom [modernize-use-std-erase]
+  // CHECK-FIXES: std::erase(d, 42);
+}
+
+void test_standard_remove_if_idiom() {
+  std::vector<int> v;
+  auto IsNegative = [](int x) { return x < 0; };
+  
+  v.erase(std::remove_if(v.begin(), v.end(), IsNegative), v.end());
+  // CHECK-MESSAGES: {{.*}}: warning: prefer std::erase_if over the erase-remove_if idiom [modernize-use-std-erase]
+  // CHECK-FIXES: std::erase_if(v, IsNegative);
+
+  std::list<int> l;
+  l.erase(std::remove_if(l.begin(), l.end(), IsNegative), l.end());
+  // CHECK-MESSAGES: {{.*}}: warning: prefer std::erase_if over the erase-remove_if idiom [modernize-use-std-erase]
+  // CHECK-FIXES: std::erase_if(l, IsNegative);
+}
+
+void test_string_special_case() {
+  std::string s;
+  s.erase(std::remove(s.begin(), s.end(), ' '), s.end());
+  // CHECK-MESSAGES: {{.*}}: warning: prefer std::erase over the erase-remove idiom [modernize-use-std-erase]
+  // CHECK-FIXES: std::erase(s, ' ');
+}
+
+auto IsEven = [](int i) { return i % 2 == 0; };
+
+void test_remove_negative_cases() {
+  std::vector<int> v;
+  std::vector<int> v2;
+
+  v.erase(std::remove_if(v.rbegin(), v.rend(), IsEven), v.rend());
+  // CHECK-FIXES: v.erase(std::remove_if(v.rbegin(), v.rend(), IsEven), v.rend());
+
+  MyContainer<int> c;
+  c.erase(std::remove_if(c.begin(), c.end(), IsEven), c.end());
+  // CHECK-FIXES: c.erase(std::remove_if(c.begin(), c.end(), IsEven), c.end());
+
+  v.erase(std::remove_if(v.begin() + 1, v.end(), IsEven), v.end());
+  // CHECK-FIXES: v.erase(std::remove_if(v.begin() + 1, v.end(), IsEven), v.end());
+  //
+  v.erase(std::remove(v2.begin(), v2.end(), 1), v.end());
+  // CHECK-FIXES: v.erase(std::remove(v2.begin(), v2.end(), 1), v.end());
+
+  v.erase(std::remove(v.begin(), v.end(), 1), v.end() - 1);
+  // CHECK-FIXES: v.erase(std::remove(v.begin(), v.end(), 1), v.end() - 1);
+
+  auto it = std::remove(v.begin(), v.end(), 1);
+  v.erase(it, v.end());
+  // CHECK-FIXES: v.erase(it, v.end());
+}

>From 9fea68f83501d07d8cded27d30fd6eb6fecb7f7d Mon Sep 17 00:00:00 2001
From: Omar Ibrahim <moar.ahmed at gmail.com>
Date: Wed, 22 Apr 2026 07:19:11 +0200
Subject: [PATCH 2/5] clang-format

---
 clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index abd2f11a44e48..28493994eb775 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -16,7 +16,6 @@
 #include "ConcatNestedNamespacesCheck.h"
 #include "DeprecatedHeadersCheck.h"
 #include "DeprecatedIosBaseAliasesCheck.h"
-#include "UseStdEraseCheck.h"
 #include "LoopConvertCheck.h"
 #include "MacroToEnumCheck.h"
 #include "MakeSharedCheck.h"
@@ -49,6 +48,7 @@
 #include "UseScopedLockCheck.h"
 #include "UseStartsEndsWithCheck.h"
 #include "UseStdBitCheck.h"
+#include "UseStdEraseCheck.h"
 #include "UseStdFormatCheck.h"
 #include "UseStdNumbersCheck.h"
 #include "UseStdPrintCheck.h"

>From fa7cc6a3470a0d2c369f7c82e3b0d97d1a8d0b0f Mon Sep 17 00:00:00 2001
From: Omar Ibrahim <moar.ahmed at gmail.com>
Date: Wed, 22 Apr 2026 07:28:24 +0200
Subject: [PATCH 3/5] make matcher stricter, remove unused include, adjust
 unsorted release note

---
 .../clang-tidy/modernize/UseStdEraseCheck.cpp          |  3 +--
 clang-tools-extra/docs/ReleaseNotes.rst                | 10 +++++-----
 2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.cpp
index 6e2b73054561e..4a6d2b8fef46c 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.cpp
@@ -17,7 +17,6 @@
 #include "clang/Lex/Lexer.h"
 #include "llvm/ADT/StringRef.h"
 #include <initializer_list>
-#include <string_view>
 
 using namespace clang::ast_matchers;
 
@@ -76,7 +75,7 @@ void UseStdEraseCheck::registerMatchers(MatchFinder *Finder) {
 
   Finder->addMatcher(
       cxxMemberCallExpr(callee(cxxMethodDecl(hasName("erase"))),
-                        hasArgument(0, makeMatcherPair()),
+                        argumentCountIs(2), hasArgument(0, makeMatcherPair()),
                         hasArgument(1, EraseEndCheck),
                         on(anyOf(hasType(EraseableContainerType),
                                  hasType(pointsTo(EraseableContainerType)))))
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 2c18b3e08813c..baa4cd6b191f9 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -145,17 +145,17 @@ New checks
   ``llvm::to_vector(llvm::make_filter_range(...))`` that can be replaced with
   ``llvm::map_to_vector`` and ``llvm::filter_to_vector``.
 
-- New :doc:`modernize-use-std-erase
-  <clang-tidy/checks/modernize/use-std-erase>` check.
-
-  Replaces erase-remove idiom with C++20's' std::erase and std::erase_if for improved readability.
-
 - New :doc:`modernize-use-std-bit
   <clang-tidy/checks/modernize/use-std-bit>` check.
 
   Finds common idioms which can be replaced by standard functions from the
   ``<bit>`` C++20 header.
 
+- New :doc:`modernize-use-std-erase
+  <clang-tidy/checks/modernize/use-std-erase>` check.
+
+  Replaces erase-remove idiom with C++20's' std::erase and std::erase_if for improved readability.
+
 - New :doc:`modernize-use-string-view
   <clang-tidy/checks/modernize/use-string-view>` check.
 

>From 8ec5415f3fd8e60ce16a749c7146bfc9b47d7d1f Mon Sep 17 00:00:00 2001
From: Omar Ibrahim <moar.ahmed at gmail.com>
Date: Sun, 26 Apr 2026 11:03:03 +0200
Subject: [PATCH 4/5] address comments

---
 .../clang-tidy/modernize/CMakeLists.txt       |  2 +-
 .../modernize/ModernizeTidyModule.cpp         |  2 +-
 .../clang-tidy/modernize/UseStdEraseCheck.cpp | 28 ++++++------
 clang-tools-extra/docs/ReleaseNotes.rst       |  2 +-
 .../checks/modernize/use-std-erase.rst        |  2 +-
 .../checkers/modernize/use-std-erase.cpp      | 43 +++----------------
 6 files changed, 22 insertions(+), 57 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index 45cf3d1c7ad92..6abfc38f413c6 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -12,7 +12,6 @@ add_clang_library(clangTidyModernizeModule STATIC
   ConcatNestedNamespacesCheck.cpp
   DeprecatedHeadersCheck.cpp
   DeprecatedIosBaseAliasesCheck.cpp
-  UseStdEraseCheck.cpp
   IntegralLiteralExpressionMatcher.cpp
   LoopConvertCheck.cpp
   LoopConvertUtils.cpp
@@ -49,6 +48,7 @@ add_clang_library(clangTidyModernizeModule STATIC
   UseScopedLockCheck.cpp
   UseStartsEndsWithCheck.cpp
   UseStdBitCheck.cpp
+  UseStdEraseCheck.cpp
   UseStdFormatCheck.cpp
   UseStdNumbersCheck.cpp
   UseStdPrintCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index 28493994eb775..001f91f6f7337 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -82,7 +82,6 @@ class ModernizeModule : public ClangTidyModule {
         "modernize-deprecated-headers");
     CheckFactories.registerCheck<DeprecatedIosBaseAliasesCheck>(
         "modernize-deprecated-ios-base-aliases");
-    CheckFactories.registerCheck<UseStdEraseCheck>("modernize-use-std-erase");
     CheckFactories.registerCheck<LoopConvertCheck>("modernize-loop-convert");
     CheckFactories.registerCheck<MacroToEnumCheck>("modernize-macro-to-enum");
     CheckFactories.registerCheck<MakeSharedCheck>("modernize-make-shared");
@@ -100,6 +99,7 @@ class ModernizeModule : public ClangTidyModule {
     CheckFactories.registerCheck<UseStartsEndsWithCheck>(
         "modernize-use-starts-ends-with");
     CheckFactories.registerCheck<UseStdBitCheck>("modernize-use-std-bit");
+    CheckFactories.registerCheck<UseStdEraseCheck>("modernize-use-std-erase");
     CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format");
     CheckFactories.registerCheck<UseStdNumbersCheck>(
         "modernize-use-std-numbers");
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.cpp
index 4a6d2b8fef46c..7349bd932e83f 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdEraseCheck.cpp
@@ -96,27 +96,25 @@ void UseStdEraseCheck::check(const MatchFinder::MatchResult &Result) {
   if (!EraseMethod)
     return;
 
-  const std::string RemoveFuncName =
-      RemoveCall->getDirectCallee()->getName().str();
+  const StringRef RemoveFuncName = RemoveCall->getDirectCallee()->getName();
 
-  const std::string ReplacementFreeFunc =
+  const StringRef ReplacementFreeFunc =
       RemoveFuncName == "remove" ? "std::erase" : "std::erase_if";
 
   std::string Replacement =
-      ReplacementFreeFunc + "(" +
-      Lexer::getSourceText(
-          CharSourceRange::getTokenRange(ContainerThis->getSourceRange()),
-          Result.Context->getSourceManager(), Result.Context->getLangOpts())
-          .str() +
-      ", " +
-      Lexer::getSourceText(
-          CharSourceRange::getTokenRange(ValueOrCond->getSourceRange()),
-          Result.Context->getSourceManager(), Result.Context->getLangOpts())
-          .str() +
-      ")";
+      (ReplacementFreeFunc + "(" +
+       Lexer::getSourceText(
+           CharSourceRange::getTokenRange(ContainerThis->getSourceRange()),
+           Result.Context->getSourceManager(), Result.Context->getLangOpts()) +
+       ", " +
+       Lexer::getSourceText(
+           CharSourceRange::getTokenRange(ValueOrCond->getSourceRange()),
+           Result.Context->getSourceManager(), Result.Context->getLangOpts()) +
+       ")")
+          .str();
 
   diag(EraseCall->getExprLoc(),
-       "prefer %0 over the erase-" + RemoveFuncName + " idiom")
+       ("prefer %0 over the erase-" + RemoveFuncName + " idiom").str())
       << ReplacementFreeFunc
       << FixItHint::CreateReplacement(EraseCall->getSourceRange(), Replacement);
 }
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index baa4cd6b191f9..53d63cf3b0d55 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -154,7 +154,7 @@ New checks
 - New :doc:`modernize-use-std-erase
   <clang-tidy/checks/modernize/use-std-erase>` check.
 
-  Replaces erase-remove idiom with C++20's' std::erase and std::erase_if for improved readability.
+  Replaces erase-remove idiom with C++20's' `std::erase` and `std::erase_if` for improved readability.
 
 - New :doc:`modernize-use-string-view
   <clang-tidy/checks/modernize/use-string-view>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-erase.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-erase.rst
index 9bd3fb26035de..99184e8126c57 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-erase.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-erase.rst
@@ -3,7 +3,7 @@
 modernize-use-std-erase
 =======================
 
-Replaces erase-remove idiom with C++20's' std::erase and std::erase_if 
+Replaces erase-remove idiom with C++20's' `std::erase` and `std::erase_if`
 for improved readability.
 
 Covered scenarios:
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-erase.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-erase.cpp
index afce0c1c9d243..0839bfcb87b0f 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-erase.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-erase.cpp
@@ -1,43 +1,10 @@
 // RUN: %check_clang_tidy -std=c++20 %s modernize-use-std-erase %t
+#include <deque>
+#include <list>
+#include <string>
+#include <vector>
 
 namespace std {
-
-template <typename T>
-struct vector {
-  using iterator = T*;
-  using reverse_iterator = T*;
-  iterator begin();
-  iterator end();
-  reverse_iterator rbegin();
-  reverse_iterator rend();
-  iterator erase(iterator, iterator);
-};
-
-template <typename T>
-struct deque {
-  using iterator = T*;
-  iterator begin();
-  iterator end();
-  iterator erase(iterator, iterator);
-};
-
-template <typename T>
-struct list {
-  using iterator = T*;
-  iterator begin();
-  iterator end();
-  iterator erase(iterator, iterator);
-};
-
-template <typename T>
-struct basic_string {
-  using iterator = T*;
-  iterator begin();
-  iterator end();
-  iterator erase(iterator, iterator);
-};
-using string = basic_string<char>;
-
 template <class ForwardIt, class T>
 ForwardIt remove(ForwardIt first, ForwardIt last, const T& value);
 
@@ -109,7 +76,7 @@ void test_remove_negative_cases() {
 
   v.erase(std::remove_if(v.begin() + 1, v.end(), IsEven), v.end());
   // CHECK-FIXES: v.erase(std::remove_if(v.begin() + 1, v.end(), IsEven), v.end());
-  //
+ 
   v.erase(std::remove(v2.begin(), v2.end(), 1), v.end());
   // CHECK-FIXES: v.erase(std::remove(v2.begin(), v2.end(), 1), v.end());
 

>From 55004005d8a17ce1378d44abeb98b2c396874a34 Mon Sep 17 00:00:00 2001
From: Omar Ibrahim <moar.ahmed at gmail.com>
Date: Sun, 26 Apr 2026 11:09:41 +0200
Subject: [PATCH 5/5] fix issues in docs/release notes

---
 clang-tools-extra/docs/ReleaseNotes.rst            |  2 +-
 .../clang-tidy/checks/modernize/use-std-erase.rst  | 14 +++++++-------
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 53d63cf3b0d55..f64e225f701e5 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -154,7 +154,7 @@ New checks
 - New :doc:`modernize-use-std-erase
   <clang-tidy/checks/modernize/use-std-erase>` check.
 
-  Replaces erase-remove idiom with C++20's' `std::erase` and `std::erase_if` for improved readability.
+  Replaces erase-remove idiom with C++20's `std::erase` and `std::erase_if` for improved readability.
 
 - New :doc:`modernize-use-string-view
   <clang-tidy/checks/modernize/use-string-view>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-erase.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-erase.rst
index 99184e8126c57..b0a1f26bdb45b 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-erase.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-erase.rst
@@ -3,14 +3,14 @@
 modernize-use-std-erase
 =======================
 
-Replaces erase-remove idiom with C++20's' `std::erase` and `std::erase_if`
+Replaces erase-remove idiom with C++20's `std::erase` and `std::erase_if`
 for improved readability.
 
 Covered scenarios:
 
-========================================================== ============================
-Expression                                                 Replacement
----------------------------------------------------------- ----------------------------
-``v.erase(std::remove(v.begin(), v.end(), 5), v.end())``   ``std::erase(v, 5)``
-``l.erase(std::remove_if(v.begin(), v.end(), 5), isEven)`` ``std::erase_if(v, isEven)``
-========================================================== ============================
+================================================================ ============================
+Expression                                                       Replacement
+---------------------------------------------------------------- ----------------------------
+``v.erase(std::remove(v.begin(), v.end(), 5), v.end())``         ``std::erase(v, 5)``
+``l.erase(std::remove_if(l.begin(), l.end(), isEven), l.end())`` ``std::erase_if(l, isEven)``
+================================================================ ============================



More information about the cfe-commits mailing list