Skip to content

Commit 96cd370

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 696d4f9 commit 96cd370

File tree

4 files changed

+115
-8
lines changed

4 files changed

+115
-8
lines changed

clang/docs/ReleaseNotes.rst

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

205208
C Language Changes
206209
------------------

clang/lib/Sema/SemaInit.cpp

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

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

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

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

clang/test/CXX/drs/dr23xx.cpp

Lines changed: 84 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,80 @@ 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 expressions 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; // since-cxx17-note {{'X' has been explicitly marked deleted here}}
127+
};
128+
129+
X<std::initializer_list<any>> x{ X<std::initializer_list<any>>() }; // since-cxx17-error {{call to deleted constructor of 'X<std::initializer_list<any>>'}}
130+
131+
#endif
132+
}
133+
50134
// dr2331: na
51135

52136
#if __cplusplus >= 201103L

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)