[clang] [clang] Implement CWG3135 - constexpr structured bindings with prvalues from tuples (PR #191880)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Apr 13 12:50:28 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Matthias Wippich (Tsche)
<details>
<summary>Changes</summary>
This patch implements [CWG3135](https://cplusplus.github.io/CWG/issues/3135.html). This has been accepted into C++26 at the Croydon meeting through [CWG Motion 3](https://github.com/cplusplus/draft/issues/8824).
This change has not been designated a defect report. However, there is currently some discussion on the core reflector regarding this designation - GCC implements it as DR. Please advise whether we should also treat this as a DR (it would break a handful of tests).
---
Full diff: https://github.com/llvm/llvm-project/pull/191880.diff
5 Files Affected:
- (modified) clang/docs/ReleaseNotes.rst (+3)
- (modified) clang/lib/Sema/SemaDeclCXX.cpp (+19-13)
- (modified) clang/test/CodeGenCXX/bad-codegen-for-constexpr-structured-bindings.cpp (+1-1)
- (added) clang/test/SemaCXX/cxx2c-decomposition-prvalues.cpp (+37)
- (modified) clang/test/SemaCXX/cxx2c-decomposition.cpp (+2-2)
``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index fd58d7847717c..63c27d1fb7066 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -49,6 +49,9 @@ C++ Specific Potentially Breaking Changes
- Clang now correctly rejects ``export`` declarations in module implementation
partitions. (#GH107602)
+- Clang now uses non-reference types for structured bindings whose initializer
+ returns a prvalue.
+
ABI Changes in This Version
---------------------------
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 6647e52535114..f07e4d66d61d8 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -1361,25 +1361,28 @@ static bool checkTupleLikeDecomposition(Sema &S,
return true;
Expr *Init = E.get();
- // Given the type T designated by std::tuple_element<i - 1, E>::type,
+ // Given the type T designated by std::tuple_element<i - 1, E>::type
QualType T = getTupleLikeElementType(S, Loc, I, DecompType);
if (T.isNull())
return true;
- // each vi is a variable of type "reference to T" initialized with the
- // initializer, where the reference is an lvalue reference if the
- // initializer is an lvalue and an rvalue reference otherwise
- QualType RefType =
- S.BuildReferenceType(T, E.get()->isLValue(), Loc, B->getDeclName());
- if (RefType.isNull())
+ // C++26 [dcl.struct.bind]p7:
+ // and the type Ui, defined as Ti if the initializer is a prvalue,
+ // as "lvalue reference to Ti" if the initializer is an lvalue,
+ // or as "rvalue reference to Ti" otherwise
+ // "defined as Ti if the initializer is a prvalue" was introduced by CWG3135
+ QualType U = E.get()->isPRValue() && S.getLangOpts().CPlusPlus26
+ ? T
+ : S.BuildReferenceType(T, E.get()->isLValue(), Loc,
+ B->getDeclName());
+ if (U.isNull())
return true;
// Don't give this VarDecl a TypeSourceInfo, since this is a synthesized
// entity and this type was never written in source code.
- auto *RefVD =
- VarDecl::Create(S.Context, Src->getDeclContext(), Loc, Loc,
- B->getDeclName().getAsIdentifierInfo(), RefType,
- /*TInfo=*/nullptr, Src->getStorageClass());
+ auto *RefVD = VarDecl::Create(S.Context, Src->getDeclContext(), Loc, Loc,
+ B->getDeclName().getAsIdentifierInfo(), U,
+ /*TInfo=*/nullptr, Src->getStorageClass());
RefVD->setLexicalDeclContext(Src->getLexicalDeclContext());
RefVD->setTSCSpec(Src->getTSCSpec());
RefVD->setImplicit();
@@ -1388,7 +1391,10 @@ static bool checkTupleLikeDecomposition(Sema &S,
RefVD->getLexicalDeclContext()->addHiddenDecl(RefVD);
InitializedEntity Entity = InitializedEntity::InitializeBinding(RefVD);
- InitializationKind Kind = InitializationKind::CreateCopy(Loc, Loc);
+ InitializationKind Kind =
+ E.get()->isPRValue() && S.getLangOpts().CPlusPlus26
+ ? InitializationKind::CreateDirect(Loc, Loc, Loc)
+ : InitializationKind::CreateCopy(Loc, Loc);
InitializationSequence Seq(S, Entity, Kind, Init);
E = Seq.Perform(S, Entity, Kind, Init);
if (E.isInvalid())
@@ -1405,7 +1411,7 @@ static bool checkTupleLikeDecomposition(Sema &S,
if (E.isInvalid())
return true;
- B->setBinding(T, E.get());
+ B->setBinding(U, E.get());
I++;
}
diff --git a/clang/test/CodeGenCXX/bad-codegen-for-constexpr-structured-bindings.cpp b/clang/test/CodeGenCXX/bad-codegen-for-constexpr-structured-bindings.cpp
index ce81f7d8d026e..87c2055627aa7 100644
--- a/clang/test/CodeGenCXX/bad-codegen-for-constexpr-structured-bindings.cpp
+++ b/clang/test/CodeGenCXX/bad-codegen-for-constexpr-structured-bindings.cpp
@@ -35,6 +35,6 @@ const u8 &f() {
return I;
}
-// CHECK: @[[TMP:_ZGR.*]] = internal constant i8 0, align 1
+// CHECK: @[[TMP:_ZZ1fvE1I]] = internal constant i8 0, align 1
// CHECK-LABEL: define {{.*}} @_Z1fv(
// CHECK: ret ptr @[[TMP]]
diff --git a/clang/test/SemaCXX/cxx2c-decomposition-prvalues.cpp b/clang/test/SemaCXX/cxx2c-decomposition-prvalues.cpp
new file mode 100644
index 0000000000000..85ed2c70c3528
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-decomposition-prvalues.cpp
@@ -0,0 +1,37 @@
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
+// expected-no-diagnostics
+// RUN: %clang_cc1 -std=c++2c -fsyntax-only -verify=since-cxx26 %s
+
+
+namespace std {
+ using size_t = decltype(sizeof(0));
+ template<typename> struct tuple_size;
+ template<size_t, typename> struct tuple_element;
+}
+
+struct Pinned {
+ Pinned(const Pinned&) = delete;
+ // since-cxx26-note at -1 {{'Pinned' has been explicitly marked deleted here}}
+ Pinned& operator=(const Pinned&) = delete;
+};
+
+struct Source {
+ operator Pinned&&() const;
+
+ template<std::size_t>
+ Source get() noexcept;
+};
+
+template<>
+struct std::tuple_size<Source> {
+ static constexpr std::size_t value = 1;
+};
+
+template<>
+struct std::tuple_element<0, Source> { using type = Pinned; };
+
+// CWG3135: In C++26 mode `x` is of type Pinned rather than Pinned&&.
+// This leads to the deleted copy ctor being called in C++26 mode.
+auto [x] = Source{};
+// since-cxx26-error at -1 {{call to deleted constructor of 'std::tuple_element<0UL, Source>::type' (aka 'Pinned')}}
+// since-cxx26-note at -2 {{in implicit initialization of binding declaration 'x'}}
\ No newline at end of file
diff --git a/clang/test/SemaCXX/cxx2c-decomposition.cpp b/clang/test/SemaCXX/cxx2c-decomposition.cpp
index 99278c6575ef1..df2e3fa90263a 100644
--- a/clang/test/SemaCXX/cxx2c-decomposition.cpp
+++ b/clang/test/SemaCXX/cxx2c-decomposition.cpp
@@ -66,8 +66,8 @@ void test() {
constexpr auto [a, b] = B{};
static_assert(a.n == 0);
// expected-error at -1 {{static assertion expression is not an integral constant expression}} \
-// expected-note at -1 {{read of temporary is not allowed in a constant expression outside the expression that created the temporary}}\
-// expected-note at -2 {{temporary created here}}
+// expected-note at -1 {{read of non-constexpr variable 'a' is not allowed in a constant expression}} \
+// expected-note at -2 {{declared here}}
constinit auto [init1] = Y {42};
constinit auto [init2] = X {}; // expected-error {{variable does not have a constant initializer}} \
``````````
</details>
https://github.com/llvm/llvm-project/pull/191880
More information about the cfe-commits
mailing list