[clang] Implement CWG3135 - constexpr structured bindings with prvalues from tuples (#191880)

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 was
recently some discussion about this designation and GCC implements it as
DR. Therefore, this is treated as DR regardless.
This commit is contained in:
Matthias Wippich 2026-05-21 16:16:40 +02:00 committed by GitHub
parent 329972b309
commit 27249aa2d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 99 additions and 66 deletions

View file

@ -207,6 +207,9 @@ Resolutions to C++ Defect Reports
- Clang now allows omitting ``typename`` before a template name in a
conversion operator, implementing `CWG2413 <https://wg21.link/cwg2413>`_.
- Clang now uses non-reference types for structured bindings whose initializer
returns a prvalue. This resolves `CWG3135 <https://wg21.link/cwg3135>`_.
C Language Changes
------------------

View file

@ -2602,7 +2602,7 @@ def select_initialized_entity_kind : TextSubstitution<
"returning object|initializing statement expression result|"
"throwing object|copying member subobject|copying array element|"
"allocating object|copying temporary|initializing base subobject|"
"initializing vector element|capturing value}0">;
"initializing vector element|capturing value|||||||||initializing binding}0">;
def err_temp_copy_no_viable : Error<
"no viable constructor %sub{select_initialized_entity_kind}0 of type %1">;

View file

@ -255,7 +255,7 @@ public:
// If this is the holding variable for a `BindingDecl`, we may already
// have a storage location set up -- so check. (See also explanation below
// where we process the `BindingDecl`.)
if (D.getType()->isReferenceType() && Env.getStorageLocation(D) != nullptr)
if (D.isImplicit() && Env.getStorageLocation(D) != nullptr)
return;
assert(Env.getStorageLocation(D) == nullptr);

View file

@ -1380,33 +1380,37 @@ 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()
? 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 =
auto *BindingVD =
VarDecl::Create(S.Context, Src->getDeclContext(), Loc, Loc,
B->getDeclName().getAsIdentifierInfo(), RefType,
B->getDeclName().getAsIdentifierInfo(), U,
/*TInfo=*/nullptr, Src->getStorageClass());
RefVD->setLexicalDeclContext(Src->getLexicalDeclContext());
RefVD->setTSCSpec(Src->getTSCSpec());
RefVD->setImplicit();
BindingVD->setLexicalDeclContext(Src->getLexicalDeclContext());
BindingVD->setTSCSpec(Src->getTSCSpec());
BindingVD->setImplicit();
if (Src->isInlineSpecified())
RefVD->setInlineSpecified();
RefVD->getLexicalDeclContext()->addHiddenDecl(RefVD);
BindingVD->setInlineSpecified();
BindingVD->getLexicalDeclContext()->addHiddenDecl(BindingVD);
InitializedEntity Entity = InitializedEntity::InitializeBinding(RefVD);
InitializedEntity Entity = InitializedEntity::InitializeBinding(BindingVD);
InitializationKind Kind = InitializationKind::CreateCopy(Loc, Loc);
InitializationSequence Seq(S, Entity, Kind, Init);
E = Seq.Perform(S, Entity, Kind, Init);
@ -1415,12 +1419,11 @@ static bool checkTupleLikeDecomposition(Sema &S,
E = S.ActOnFinishFullExpr(E.get(), Loc, /*DiscardedValue*/ false);
if (E.isInvalid())
return true;
RefVD->setInit(E.get());
S.CheckCompleteVariableDeclaration(RefVD);
BindingVD->setInit(E.get());
S.CheckCompleteVariableDeclaration(BindingVD);
E = S.BuildDeclarationNameExpr(CXXScopeSpec(),
DeclarationNameInfo(B->getDeclName(), Loc),
RefVD);
E = S.BuildDeclarationNameExpr(
CXXScopeSpec(), DeclarationNameInfo(B->getDeclName(), Loc), BindingVD);
if (E.isInvalid())
return true;

View file

@ -77,13 +77,11 @@ int main() {
// CHECK-NEXT: 7: [B3.6] (ImplicitCastExpr, FunctionToPointerDecay, tuple_element<0L, pair<int, int> >::type (*)(pair<int, int> &))
// CHECK-NEXT: 8: decomposition-a-b
// CHECK-NEXT: 9: [B3.7]([B3.8])
// CHECK-NEXT: 10: [B3.9]
// CHECK-NEXT: 11: std::tuple_element<0UL, std::pair<int, int>>::type &&a = get<0UL>(decomposition-a-b);
// CHECK-NEXT: 12: get<1UL>
// CHECK-NEXT: 13: [B3.12] (ImplicitCastExpr, FunctionToPointerDecay, tuple_element<1L, pair<int, int> >::type (*)(pair<int, int> &))
// CHECK-NEXT: 14: decomposition-a-b
// CHECK-NEXT: 15: [B3.13]([B3.14])
// CHECK-NEXT: 16: [B3.15]
// CHECK-NEXT: 17: std::tuple_element<1UL, std::pair<int, int>>::type &&b = get<1UL>(decomposition-a-b);
// CHECK-NEXT: 10: std::tuple_element<0UL, std::pair<int, int>>::type a = get<0UL>(decomposition-a-b);
// CHECK-NEXT: 11: get<1UL>
// CHECK-NEXT: 12: [B3.11] (ImplicitCastExpr, FunctionToPointerDecay, tuple_element<1L, pair<int, int> >::type (*)(pair<int, int> &))
// CHECK-NEXT: 13: decomposition-a-b
// CHECK-NEXT: 14: [B3.12]([B3.13])
// CHECK-NEXT: 15: std::tuple_element<1UL, std::pair<int, int>>::type b = get<1UL>(decomposition-a-b);
// CHECK-NEXT: Preds (1): B1
// CHECK-NEXT: Succs (1): B2

View file

@ -108,19 +108,8 @@ namespace std {
};
}
void no_warning_on_tuple_types_copy(Mytuple t) {
auto [a, b] = t; // no-warning
}
Mytuple getMytuple();
void deconstruct_tuple_types_warning() {
// The initializers reference the decomposed region, so the warning is not reported
// FIXME: ideally we want to ignore that the initializers reference the decomposed region, and report the warning,
// though the first step towards that is to handle DeadCode if the initializer is CXXConstructExpr.
auto [a, b] = getMytuple(); // no-warning
}
int deconstruct_tuple_types_no_warning() {
auto [a, b] = getMytuple(); // no-warning
return a + b;

View file

@ -222,9 +222,6 @@ template<> struct std::tuple_size<constant::Q> { static const int value = 3; };
template<int N> struct std::tuple_element<N, constant::Q> { typedef int type; };
namespace constant {
Q q;
// This creates and lifetime-extends a temporary to hold the result of each get() call.
auto [a, b, c] = q; // expected-note {{temporary}}
static_assert(a == 0); // expected-error {{constant expression}} expected-note {{temporary}}
constexpr bool f() {
auto [a, b, c] = q;
@ -235,7 +232,7 @@ namespace constant {
constexpr int g() {
int *p = nullptr;
{
auto [a, b, c] = q; // expected-note {{temporary created here}}
auto [a, b, c] = q; // expected-note {{declared here}}
p = &c;
}
return *p; // expected-note {{read of object outside its lifetime}}

View file

@ -1,12 +1,20 @@
// RUN: %clang_cc1 -std=c++98 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,cxx98-17
// RUN: %clang_cc1 -std=c++11 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,cxx98-17
// RUN: %clang_cc1 -std=c++14 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,cxx98-17
// RUN: %clang_cc1 -std=c++17 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,cxx98-17
// RUN: %clang_cc1 -std=c++20 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx20
// RUN: %clang_cc1 -std=c++23 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx20
// RUN: %clang_cc1 -std=c++2c -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx20
// RUN: %clang_cc1 -std=c++98 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98-14
// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98-14,cxx11-17,since-cxx11
// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98-14,cxx14-17,cxx11-17,since-cxx11,since-cxx14
// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,since-cxx17
// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx17,since-cxx20
// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx17,since-cxx20
// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx17,since-cxx20
// cxx98-17-no-diagnostics
// RUN: %clang_cc1 -std=c++98 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx98-14
// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx98-14,cxx11-17,since-cxx11
// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx98-14,cxx14-17,cxx11-17,since-cxx11,since-cxx14
// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,since-cxx17
// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx17,since-cxx20
// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx17,since-cxx20
// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx17,since-cxx20
// cxx98-14-no-diagnostics
namespace cwg3106 { // cwg3106: 2.7
#if __cplusplus >= 201103L
@ -21,6 +29,44 @@ static_assert(noexcept(noexcept(f())), "");
#endif
} // namespace cwg3128
namespace cwg3135 { // cwg3135: 23
#if __cplusplus >= 201703L
struct Pinned {
Pinned(const Pinned&) = delete; // #cwg3135-pinned-ctor
Pinned& operator=(const Pinned&) = delete;
};
struct Source {
operator Pinned&&() const;
template<int>
Source get() noexcept;
};
} // namespace cwg3135
namespace std {
template<typename> struct tuple_size;
template<int, typename> struct tuple_element;
template<>
struct tuple_size<cwg3135::Source> {
static constexpr int value = 1;
};
template<>
struct tuple_element<0, cwg3135::Source> { using type = cwg3135::Pinned; };
} // namespace std
namespace cwg3135 {
// CWG3135: `x` is of type Pinned rather than Pinned&&.
// This leads to the deleted copy ctor being called.
auto [x] = Source{};
// since-cxx17-error@-1 {{initializing binding of type 'Pinned' invokes deleted constructor}}
// since-cxx17-note@-2 {{in implicit initialization of binding declaration 'x'}}
// since-cxx17-note@#cwg3135-pinned-ctor {{'Pinned' has been explicitly marked deleted here}}
#endif
} // namespace cwg3135
namespace cwg3151 { // cwg3151: 2.7
#if __cplusplus >= 201402L
auto lambda = []{};

View file

@ -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]]

View file

@ -33,8 +33,7 @@ template<typename T> T &make();
// CHECK: @_ZDC2a12a2E ={{.*}} global {{.*}} zeroinitializer, align 4
auto [a1, a2] = make<A>();
// CHECK: @_ZDC2b12b2E ={{.*}} global {{.*}} zeroinitializer, align 1
// CHECK: @b1 ={{.*}} global ptr null, align 8
// CHECK: @_ZGR2b1_ = internal global {{.*}} zeroinitializer, align 1
// CHECK: @b1 ={{.*}} global {{.*}} zeroinitializer, align 1
// CHECK: @b2 ={{.*}} global ptr null, align 8
// CHECK: @_ZGR2b2_ = internal global i32 0, align 4
auto [b1, b2] = make<B>();
@ -50,9 +49,8 @@ auto [e1, e2] = make<E>();
// CHECK: @_Z4makeI1BERT_v()
// CHECK: call i32 @_Z3getILi0EEDa1B()
// CHECK: call void @_ZN1XC1E1Y(ptr {{[^,]*}} @_ZGR2b1_, i32
// CHECK: call i32 @__cxa_atexit({{.*}}@_ZN1XD1Ev{{.*}}@_ZGR2b1_
// CHECK: store ptr @_ZGR2b1_,
// CHECK: call void @_ZN1XC1E1Y(ptr {{[^,]*}} @b1, i32
// CHECK: call i32 @__cxa_atexit({{.*}}@_ZN1XD1Ev{{.*}}@b1
//
// CHECK: call noundef double @_Z3getILi1EEDa1B()
// CHECK: fptosi double %{{.*}} to i32
@ -149,9 +147,8 @@ int test_static_tuple() {
// CHECK: br i1
// CHECK: @__cxa_guard_acquire({{.*}} @_ZGVZ17test_static_tuplevE2x1)
// CHECK: call {{.*}} @_Z3getILi0EEDa1B(
// CHECK: call {{.*}} @_ZN1XC1E1Y({{.*}} @_ZGRZ17test_static_tuplevE2x1_,
// CHECK: call {{.*}} @__cxa_atexit({{.*}} @_ZN1XD1Ev, {{.*}} @_ZGRZ17test_static_tuplevE2x1_
// CHECK: store {{.*}} @_ZGRZ17test_static_tuplevE2x1_, {{.*}} @_ZZ17test_static_tuplevE2x1
// CHECK: call {{.*}} @_ZN1XC1E1Y({{.*}} @_ZZ17test_static_tuplevE2x1,
// CHECK: call {{.*}} @__cxa_atexit({{.*}} @_ZN1XD1Ev, {{.*}} @_ZZ17test_static_tuplevE2x1
// CHECK: call void @__cxa_guard_release({{.*}} @_ZGVZ17test_static_tuplevE2x1)
// Initialization of the secret 'x2' variable.

View file

@ -17,7 +17,7 @@
// CHECK: #dbg_declare(ptr %p, ![[VAR_13:[0-9]+]], !DIExpression()
// CHECK: getelementptr inbounds nuw %struct.A, ptr {{.*}}, i32 0, i32 1, !dbg ![[Y1_DEBUG_LOC:[0-9]+]]
// CHECK: getelementptr inbounds nuw %struct.A, ptr {{.*}}, i32 0, i32 1, !dbg ![[Y2_DEBUG_LOC:[0-9]+]]
// CHECK: load ptr, ptr %z2, {{.*}}!dbg ![[Z2_DEBUG_LOC:[0-9]+]]
// CHECK: load i32, ptr %z2, {{.*}}!dbg ![[Z2_DEBUG_LOC:[0-9]+]]
// CHECK: getelementptr inbounds [2 x i32], ptr {{.*}}, i{{64|32}} 0, i{{64|32}} 1, !dbg ![[A2_DEBUG_LOC:[0-9]+]]
// CHECK: getelementptr inbounds nuw { i32, i32 }, ptr {{.*}}, i32 0, i32 1, !dbg ![[C2_DEBUG_LOC:[0-9]+]]
// CHECK: extractelement <2 x i32> {{.*}}, i32 1, !dbg ![[V2_DEBUG_LOC:[0-9]+]]

View file

@ -66,8 +66,8 @@ void test() {
constexpr auto [a, b] = B{};
static_assert(a.n == 0);
// expected-error@-1 {{static assertion expression is not an integral constant expression}} \
// expected-note@-1 {{read of temporary is not allowed in a constant expression outside the expression that created the temporary}}\
// expected-note@-2 {{temporary created here}}
// expected-note@-1 {{read of non-constexpr variable 'a' is not allowed in a constant expression}} \
// expected-note@-2 {{declared here}}
constinit auto [init1] = Y {42};
constinit auto [init2] = X {}; // expected-error {{variable does not have a constant initializer}} \

View file

@ -21762,7 +21762,7 @@
<td>[<a href="https://wg21.link/dcl.struct.bind">dcl.struct.bind</a>]</td>
<td>accepted</td>
<td>constexpr structured bindings with prvalues from tuples</td>
<td class="unknown" align="center">Unknown</td>
<td class="unreleased" align="center">Clang 23</td>
</tr>
<tr id="3136">
<td><a href="https://cplusplus.github.io/CWG/issues/3136.html">3136</a></td>