[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:
parent
329972b309
commit
27249aa2d8
13 changed files with 99 additions and 66 deletions
|
|
@ -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
|
||||
------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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">;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
|
|
|
|||
|
|
@ -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 = []{};
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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]+]]
|
||||
|
|
|
|||
|
|
@ -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}} \
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue