[clang-tools-extra] [clang-tidy] Don't suggest const on `std::moved` locals in templates … (PR #226577)

Daniel Petrovic via cfe-commits cfe-commits at lists.llvm.org
Sun Sep 27 06:10:17 PDT 2026


https://github.com/daniel-petrovic updated https://github.com/llvm/llvm-project/pull/226577

>From 64e9f9e248b1d977a9b7b193f44615075c89bdf4 Mon Sep 17 00:00:00 2001
From: Daniel Petrovic <daniel-dev at hotmail.de>
Date: Wed, 23 Sep 2026 22:09:58 +0200
Subject: [PATCH] [clang-tidy] Don't suggest const on `std::moved` locals in
 templates (#225395)

Skip diagnostics in template instantiations for locals that are used
as arguments of std::move. Reason: for some instantiations the move
may be converted to copy so the variable may look unmutated and flagged,
whereas for other template instantiations the real move semantics may
apply.

Fixes: #225395
---
 .../clang-tidy/misc/ConstCorrectnessCheck.cpp | 30 +++++++++++++++++--
 clang-tools-extra/docs/ReleaseNotes.md        |  6 +++-
 .../misc/const-correctness-templates.cpp      | 24 +++++++++++++++
 3 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.cpp b/clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.cpp
index 57c2bda6c3fd5..fe64bc07b3e07 100644
--- a/clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.cpp
+++ b/clang-tools-extra/clang-tidy/misc/ConstCorrectnessCheck.cpp
@@ -199,6 +199,25 @@ void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
   }
 }
 
+static bool isStdMoveOperandInTemplate(const VarDecl *Variable,
+                                       const FunctionDecl *Func,
+                                       ASTContext &Context) {
+  if (Func->isTemplateInstantiation())
+    if (const Stmt *Body = Func->getBody()) {
+      // Match on the instantiated body where `std::move` is resolved to a
+      // concrete function and the argument references the instantiated
+      // variable.
+      const auto Moves =
+          match(stmt(forEachDescendant(
+                    callExpr(callee(functionDecl(hasName("std::move"))),
+                             hasArgument(0, ignoringParenImpCasts(declRefExpr(
+                                                to(equalsNode(Variable)))))))),
+                *Body, Context);
+      return !Moves.empty();
+    }
+  return false;
+}
+
 static void addConstFixits(const DiagnosticBuilder &Diag,
                            const VarDecl *Variable,
                            const FunctionDecl *Function,
@@ -255,9 +274,6 @@ void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
   /// dependent. Variables like 'int x = 42;' in a template that can become
   /// const emit multiple warnings otherwise.
   bool IsNormalVariableInTemplate = Function->isTemplateInstantiation();
-  if (IsNormalVariableInTemplate &&
-      TemplateDiagnosticsCache.contains(Variable->getBeginLoc()))
-    return;
 
   VariableCategory VC = VariableCategory::Value;
   const QualType VT = Variable->getType();
@@ -269,6 +285,14 @@ void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
            ArrayT && ArrayT->getElementType()->isPointerType())
     VC = VariableCategory::Pointer;
 
+  if (IsNormalVariableInTemplate && VC != VariableCategory::Pointer &&
+      isStdMoveOperandInTemplate(Variable, Function, *Result.Context))
+    return;
+
+  if (IsNormalVariableInTemplate &&
+      TemplateDiagnosticsCache.contains(Variable->getBeginLoc()))
+    return;
+
   const auto CheckValue = [&]() {
     // Offload const-analysis to utility function.
     if (isMutated(Variable, LocalScope, Function, Result.Context))
diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md
index 833638a47abc6..240c9908ede35 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -212,7 +212,11 @@ infrastructure are described first, followed by tool-specific sections.
 
   - No longer diagnoses variables declared with `decltype(auto)`, where the
     suggested `const` does not compile.
-    
+
+  - No longer diagnoses variables used as operands of `std::move` in template
+    instantiations, where the suggested `const` would break the move for
+    instantiations with real move semantics.
+
 - Fixed an infinite loop in {doc}`misc-multiple-inheritance
   <clang-tidy/checks/misc/multiple-inheritance>` when checking a class that
   inherits from itself or has a circular inheritance graph.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-templates.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-templates.cpp
index 98bf27033c3b5..c952dce7b99c5 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-templates.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/const-correctness-templates.cpp
@@ -6,6 +6,9 @@
 // RUN:   misc-const-correctness.TransformPointersAsValues: false} \
 // RUN:   }" -- -fno-delayed-template-parsing
 
+#include <string>
+#include <utility>
+
 template <typename T>
 void type_dependent_variables() {
   T value = 42;
@@ -27,6 +30,27 @@ void instantiate_template_cases() {
   type_dependent_variables<float>();
 }
 
+namespace gh225395 {
+// The variable 'tmp' is passed to std::move in the template definition, so it
+// may be moved-from for some instantiations (e.g. std::string) but std::move
+// degenerates into a copy for others (e.g. int). No warning may be emitted
+// since the fix-it would apply to the template code and break the move for
+// types with real move semantics.
+template <typename T> void my_swap(T &t1, T &t2) {
+  auto tmp = std::move(t1);
+  t1 = std::move(t2);
+  t2 = std::move(tmp);
+}
+void instantiate() {
+  std::string a;
+  std::string b;
+  my_swap(a, b);
+  int x = 0;
+  int y = 0;
+  my_swap(x, y);
+}
+} // namespace gh225395
+
 namespace gh57297{
 // The expression to check may not be the dependent operand in a dependent
 // operator.



More information about the cfe-commits mailing list