[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