[clang] [Sema] Fix lifetime extension for temporaries in range-based for loops in C++23 (PR #145164)

Marco Vitale via cfe-commits cfe-commits at lists.llvm.org
Sat Jun 21 05:25:29 PDT 2025


https://github.com/mrcvtl created https://github.com/llvm/llvm-project/pull/145164

C++23 mandates that temporaries used in range-based for loops are lifetime-extended
to cover the full loop. This patch adds a check for loop variables and compiler-
generated `__range` bindings to apply the correct extension.

Includes test cases based on examples from CWG900/P2644R1.


>From 4398de927292be66f8f54c93c1064b6230f5470a Mon Sep 17 00:00:00 2001
From: Marco Vitale <mar.vitale at icloud.com>
Date: Sat, 21 Jun 2025 14:01:53 +0200
Subject: [PATCH] [Sema] Fix lifetime extension for temporaries in range-based
 for loops in C++23

C++23 mandates that temporaries used in range-based for loops are lifetime-extended
to cover the full loop. This patch adds a check for loop variables and compiler-
generated `__range` bindings to apply the correct extension.

Includes test cases based on examples from CWG900/P2644R1.
---
 clang/lib/Sema/CheckExprLifetime.cpp          | 28 +++++++
 .../test/SemaCXX/range-for-lifetime-cxx23.cpp | 79 +++++++++++++++++++
 2 files changed, 107 insertions(+)
 create mode 100644 clang/test/SemaCXX/range-for-lifetime-cxx23.cpp

diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index 060ba31660556..0434aa0c29c26 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -57,6 +57,31 @@ enum LifetimeKind {
 };
 using LifetimeResult =
     llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>;
+
+/// Returns true if the given entity is part of a range-based for loop and
+/// should trigger lifetime extension under C++23 rules.
+///
+/// This handles both explicit range loop variables and internal compiler-
+/// generated variables like `__range1`.
+static bool
+isRangeBasedForLoopVariable(const Sema &SemaRef,
+                            const InitializedEntity *ExtendingEntity) {
+  if (!SemaRef.getLangOpts().CPlusPlus23)
+    return false;
+
+  const Decl *EntityDecl = ExtendingEntity->getDecl();
+  if (!EntityDecl)
+    return false;
+
+  if (const auto *VD = dyn_cast<VarDecl>(EntityDecl)) {
+    if (VD->isCXXForRangeDecl() || VD->getName().starts_with("__range")) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 } // namespace
 
 /// Determine the declaration which an initialized entity ultimately refers to,
@@ -1341,6 +1366,9 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
       }
 
       if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
+        if (isRangeBasedForLoopVariable(SemaRef, ExtendingEntity))
+          return true;
+
         SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
             << DiagRange;
         return false;
diff --git a/clang/test/SemaCXX/range-for-lifetime-cxx23.cpp b/clang/test/SemaCXX/range-for-lifetime-cxx23.cpp
new file mode 100644
index 0000000000000..bb6e06ec4517c
--- /dev/null
+++ b/clang/test/SemaCXX/range-for-lifetime-cxx23.cpp
@@ -0,0 +1,79 @@
+// RUN: %clangxx -std=c++23 -fsyntax-only -Xclang -verify %s
+
+#include <string>
+#include <vector>
+#include <map>
+#include <tuple>
+#include <optional>
+#include <variant>
+#include <array>
+#include <span>
+
+static std::vector<std::string> getVector() {
+  return {"first", "second", "third"};
+}
+
+static std::map<std::string, std::vector<int>> getMap() {
+  return {{"key", {1, 2, 3}}};
+}
+
+static std::tuple<std::vector<double>> getTuple() {
+  return std::make_tuple(std::vector<double>{3.14, 2.71});
+}
+
+static std::optional<std::vector<char>> getOptionalColl() {
+  return std::vector<char>{'x', 'y', 'z'};
+}
+
+static std::variant<std::string, int> getVariant() {
+  return std::string("variant");
+}
+
+static const std::array<int, 4>& arrOfConst() {
+  static const std::array<int, 4> arr = {10, 20, 30, 40};
+  return arr;
+}
+
+static void testGetVectorSubscript() {
+  for (auto e : getVector()[0]) {
+    (void)e;
+  }
+}
+
+static void testGetMapSubscript() {
+  for (auto valueElem : getMap()["key"]) {
+    (void)valueElem;
+  }
+}
+
+static void testGetTuple() {
+  for (auto e : std::get<0>(getTuple())) {
+    (void)e;
+  }
+}
+
+static void testOptionalValue() {
+  for (auto e : getOptionalColl().value()) {
+    (void)e;
+  }
+}
+
+static void testVariantGetString() {
+  for (char c : std::get<std::string>(getVariant())) {
+    (void)c;
+  }
+}
+
+static void testSpanLastFromConstArray() {
+  for (auto s : std::span{arrOfConst()}.last(2)) {
+    (void)s;
+  }
+}
+
+static void testSpanFromVectorPtr() {
+  for (auto e : std::span(getVector().data(), 2)) {
+    (void)e;
+  }
+}
+
+// expected-no-diagnostics



More information about the cfe-commits mailing list