Skip to content

Commit a3b62a4

Browse files
committed
[SemaCXX] Allow some copy elision of T{ T_prvalue } if an initializer-list constructor isn't used (CWG2311)
Differential Revision: https://reviews.llvm.org/D156032
1 parent 644ec10 commit a3b62a4

File tree

4 files changed

+116
-8
lines changed

4 files changed

+116
-8
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ Resolutions to C++ Defect Reports
231231
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
232232
- Implemented `CWG2137 <https://wg21.link/CWG2137>`_ which allows
233233
list-initialization from objects of the same type.
234+
- Implemented `CWG2311 <https://wg21.link/CWG2311>`_: given a prvalue ``e`` of object type
235+
``T``, ``T{e}`` will try to resolve an initializer list constructor and will use it if successful (CWG2137).
236+
Otherwise, if there is no initializer list constructor, the copy will be elided as if it was ``T(e)``.
234237

235238
- Implemented `CWG2598 <https://wg21.link/CWG2598>`_ and `CWG2096 <https://wg21.link/CWG2096>`_,
236239
making unions (that have either no members or at least one literal member) literal types.

clang/lib/Sema/SemaInit.cpp

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4230,6 +4230,14 @@ static void TryConstructorInitialization(Sema &S,
42304230
Entity.getKind() !=
42314231
InitializedEntity::EK_LambdaToBlockConversionBlockElement);
42324232

4233+
bool CopyElisionPossible = false;
4234+
auto ElideConstructor = [&] {
4235+
// Convert qualifications if necessary.
4236+
Sequence.AddQualificationConversionStep(DestType, VK_PRValue);
4237+
if (ILE)
4238+
Sequence.RewrapReferenceInitList(DestType, ILE);
4239+
};
4240+
42334241
// C++17 [dcl.init]p17:
42344242
// - If the initializer expression is a prvalue and the cv-unqualified
42354243
// version of the source type is the same class as the class of the
@@ -4240,13 +4248,19 @@ static void TryConstructorInitialization(Sema &S,
42404248
// ObjC++: Lambda captured by the block in the lambda to block conversion
42414249
// should avoid copy elision.
42424250
if (S.getLangOpts().CPlusPlus17 && !RequireActualConstructor &&
4243-
Args.size() == 1 && Args[0]->isPRValue() &&
4244-
S.Context.hasSameUnqualifiedType(Args[0]->getType(), DestType)) {
4245-
// Convert qualifications if necessary.
4246-
Sequence.AddQualificationConversionStep(DestType, VK_PRValue);
4247-
if (ILE)
4248-
Sequence.RewrapReferenceInitList(DestType, ILE);
4249-
return;
4251+
UnwrappedArgs.size() == 1 && UnwrappedArgs[0]->isPRValue() &&
4252+
S.Context.hasSameUnqualifiedType(UnwrappedArgs[0]->getType(), DestType)) {
4253+
if (ILE && !DestType->isAggregateType()) {
4254+
// CWG2311: T{ prvalue_of_type_T } is not eligible for copy elision
4255+
// Make this an elision if this won't call an initializer-list
4256+
// constructor. (Always on an aggregate type or check constructors first.)
4257+
assert(!IsInitListCopy &&
4258+
"IsInitListCopy only possible with aggregate types");
4259+
CopyElisionPossible = true;
4260+
} else {
4261+
ElideConstructor();
4262+
return;
4263+
}
42504264
}
42514265

42524266
const RecordType *DestRecordType = DestType->getAs<RecordType>();
@@ -4291,6 +4305,12 @@ static void TryConstructorInitialization(Sema &S,
42914305
S, Kind.getLocation(), Args, CandidateSet, DestType, Ctors, Best,
42924306
CopyInitialization, AllowExplicit,
42934307
/*OnlyListConstructors=*/true, IsListInit, RequireActualConstructor);
4308+
4309+
if (CopyElisionPossible && Result == OR_No_Viable_Function) {
4310+
// No initializer list candidate
4311+
ElideConstructor();
4312+
return;
4313+
}
42944314
}
42954315

42964316
// C++11 [over.match.list]p1:

clang/test/CXX/drs/dr23xx.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@
66
// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx17,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors 2>&1 | FileCheck %s
77
// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx17,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors 2>&1 | FileCheck %s
88

9+
namespace std {
10+
__extension__ typedef __SIZE_TYPE__ size_t;
11+
12+
template<typename E> struct initializer_list {
13+
const E *p; size_t n;
14+
initializer_list(const E *p, size_t n);
15+
initializer_list();
16+
};
17+
}
18+
919
#if __cplusplus >= 201103L
1020
namespace dr2303 { // dr2303: 12
1121
template <typename... T>
@@ -47,6 +57,81 @@ void g() {
4757
} //namespace dr2303
4858
#endif
4959

60+
namespace dr2311 { // dr2311: 18 open
61+
#if __cplusplus >= 201707L
62+
template<typename T>
63+
void test() {
64+
// Ensure none of these try to call a move constructor.
65+
T a = T{T(0)};
66+
T b{T(0)};
67+
auto c{T(0)};
68+
T d = {T(0)};
69+
auto e = {T(0)};
70+
#if __cplusplus >= 202302L
71+
auto f = auto{T(0)};
72+
#endif
73+
void(*fn)(T);
74+
fn({T(0)});
75+
}
76+
77+
struct NonMovable {
78+
NonMovable(int);
79+
NonMovable(NonMovable&&) = delete;
80+
};
81+
struct NonMovableNonApplicableIList {
82+
NonMovableNonApplicableIList(int);
83+
NonMovableNonApplicableIList(NonMovableNonApplicableIList&&) = delete;
84+
NonMovableNonApplicableIList(std::initializer_list<int>);
85+
};
86+
struct ExplicitMovable {
87+
ExplicitMovable(int);
88+
explicit ExplicitMovable(ExplicitMovable&&);
89+
};
90+
struct ExplicitNonMovable {
91+
ExplicitNonMovable(int);
92+
explicit ExplicitNonMovable(ExplicitNonMovable&&) = delete;
93+
};
94+
struct ExplicitNonMovableNonApplicableIList {
95+
ExplicitNonMovableNonApplicableIList(int);
96+
explicit ExplicitNonMovableNonApplicableIList(ExplicitNonMovableNonApplicableIList&&) = delete;
97+
ExplicitNonMovableNonApplicableIList(std::initializer_list<int>);
98+
};
99+
struct CopyOnly {
100+
CopyOnly(int);
101+
CopyOnly(const CopyOnly&);
102+
CopyOnly(CopyOnly&&) = delete;
103+
};
104+
struct ExplicitCopyOnly {
105+
ExplicitCopyOnly(int);
106+
explicit ExplicitCopyOnly(const ExplicitCopyOnly&);
107+
explicit ExplicitCopyOnly(ExplicitCopyOnly&&) = delete;
108+
};
109+
110+
template void test<NonMovable>();
111+
template void test<NonMovableNonApplicableIList>();
112+
template void test<ExplicitMovable>();
113+
template void test<ExplicitNonMovable>();
114+
template void test<ExplicitNonMovableNonApplicableIList>();
115+
template void test<CopyOnly>();
116+
template void test<ExplicitCopyOnly>();
117+
118+
struct any {
119+
template<typename T>
120+
any(T&&);
121+
};
122+
123+
template<typename T>
124+
struct X {
125+
X();
126+
X(T) = delete; // #dr2311-X
127+
};
128+
129+
X<std::initializer_list<any>> x{ X<std::initializer_list<any>>() };
130+
// since-cxx17-error@-1 {{call to deleted constructor of 'X<std::initializer_list<any>>'}}
131+
// since-cxx17-note@#dr2311-X {{'X' has been explicitly marked deleted here}}
132+
#endif
133+
}
134+
50135
// dr2331: na
51136
// dr2335 is in dr2335.cxx
52137

clang/www/cxx_dr_status.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13674,7 +13674,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
1367413674
<td><a href="https://cplusplus.github.io/CWG/issues/2311.html">2311</a></td>
1367513675
<td>open</td>
1367613676
<td>Missed case for guaranteed copy elision</td>
13677-
<td align="center">Not resolved</td>
13677+
<td class="unreleased" align="center">Clang 18</td>
1367813678
</tr>
1367913679
<tr id="2312">
1368013680
<td><a href="https://cplusplus.github.io/CWG/issues/2312.html">2312</a></td>

0 commit comments

Comments
 (0)