Skip to content

Commit b0b93d6

Browse files
MitalAshokyxsamliu
authored andcommitted
[Clang] Implement CWG2137 (list-initialization from objects of the same type) (llvm#94355)
[CWG2137](https://cplusplus.github.io/CWG/issues/2137.html) This was previously implemented and then reverted in Clang 18 as llvm#77768 This also implements a workaround for [CWG2311](https://cplusplus.github.io/CWG/issues/2311.html), similarly to the 2024-03-01 comment for [CWG2742](https://cplusplus.github.io/CWG/issues/2742.html). The exact wording this tries to implement, relative to the C++26 draft: [over.match.list]p(1.2) > Otherwise, or if no viable initializer-list constructor is found <ins>and the initializer list does not consist of exactly a single element with the same cv-unqualified class type as `T`</ins>, overload resolution is performed again, [...] [dcl.init.list]p(3.7) > Otherwise, if `T` is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution. <ins>If no constructor is found and the initializer list consists of exactly a single element with the same cv-unqualified class type as `T`, the object is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization). Otherwise,</ins> if a narrowing conversion (see below) is required [...] Fixes: SWDEV-431663 Change-Id: I2eace1d80ea85171558d0a6b5ec02f21552ffb05
1 parent 25efe63 commit b0b93d6

9 files changed

+315
-35
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,16 @@ Resolutions to C++ Defect Reports
327327
- Clang now considers ``noexcept(typeid(expr))`` more carefully, instead of always assuming that ``std::bad_typeid`` can be thrown.
328328
(`CWG2191: Incorrect result for noexcept(typeid(v)) <https://cplusplus.github.io/CWG/issues/2191.html>`_).
329329

330+
- Allow calling initializer list constructors from initializer lists with
331+
a single element of the same type instead of always copying.
332+
(`CWG2137: List-initialization from object of same type <https://cplusplus.github.io/CWG/issues/2137.html>`)
333+
334+
- Speculative resolution for CWG2311 implemented so that the implementation of CWG2137 doesn't remove
335+
previous cases where guaranteed copy elision was done. Given a prvalue ``e`` of class type
336+
``T``, ``T{e}`` will try to resolve an initializer list constructor and will use it if successful.
337+
Otherwise, if there is no initializer list constructor, the copy will be elided as if it was ``T(e)``.
338+
(`CWG2311: Missed case for guaranteed copy elision <https://cplusplus.github.io/CWG/issues/2311.html>`)
339+
330340
C Language Changes
331341
------------------
332342

clang/lib/Sema/SemaInit.cpp

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4337,7 +4337,7 @@ static OverloadingResult ResolveConstructorOverload(
43374337
/// \param IsListInit Is this list-initialization?
43384338
/// \param IsInitListCopy Is this non-list-initialization resulting from a
43394339
/// list-initialization from {x} where x is the same
4340-
/// type as the entity?
4340+
/// aggregate type as the entity?
43414341
static void TryConstructorInitialization(Sema &S,
43424342
const InitializedEntity &Entity,
43434343
const InitializationKind &Kind,
@@ -4367,6 +4367,14 @@ static void TryConstructorInitialization(Sema &S,
43674367
Entity.getKind() !=
43684368
InitializedEntity::EK_LambdaToBlockConversionBlockElement);
43694369

4370+
bool CopyElisionPossible = false;
4371+
auto ElideConstructor = [&] {
4372+
// Convert qualifications if necessary.
4373+
Sequence.AddQualificationConversionStep(DestType, VK_PRValue);
4374+
if (ILE)
4375+
Sequence.RewrapReferenceInitList(DestType, ILE);
4376+
};
4377+
43704378
// C++17 [dcl.init]p17:
43714379
// - If the initializer expression is a prvalue and the cv-unqualified
43724380
// version of the source type is the same class as the class of the
@@ -4379,11 +4387,33 @@ static void TryConstructorInitialization(Sema &S,
43794387
if (S.getLangOpts().CPlusPlus17 && !RequireActualConstructor &&
43804388
UnwrappedArgs.size() == 1 && UnwrappedArgs[0]->isPRValue() &&
43814389
S.Context.hasSameUnqualifiedType(UnwrappedArgs[0]->getType(), DestType)) {
4382-
// Convert qualifications if necessary.
4383-
Sequence.AddQualificationConversionStep(DestType, VK_PRValue);
4384-
if (ILE)
4385-
Sequence.RewrapReferenceInitList(DestType, ILE);
4386-
return;
4390+
if (ILE && !DestType->isAggregateType()) {
4391+
// CWG2311: T{ prvalue_of_type_T } is not eligible for copy elision
4392+
// Make this an elision if this won't call an initializer-list
4393+
// constructor. (Always on an aggregate type or check constructors first.)
4394+
4395+
// This effectively makes our resolution as follows. The parts in angle
4396+
// brackets are additions.
4397+
// C++17 [over.match.list]p(1.2):
4398+
// - If no viable initializer-list constructor is found <and the
4399+
// initializer list does not consist of exactly a single element with
4400+
// the same cv-unqualified class type as T>, [...]
4401+
// C++17 [dcl.init.list]p(3.6):
4402+
// - Otherwise, if T is a class type, constructors are considered. The
4403+
// applicable constructors are enumerated and the best one is chosen
4404+
// through overload resolution. <If no constructor is found and the
4405+
// initializer list consists of exactly a single element with the same
4406+
// cv-unqualified class type as T, the object is initialized from that
4407+
// element (by copy-initialization for copy-list-initialization, or by
4408+
// direct-initialization for direct-list-initialization). Otherwise, >
4409+
// if a narrowing conversion [...]
4410+
assert(!IsInitListCopy &&
4411+
"IsInitListCopy only possible with aggregate types");
4412+
CopyElisionPossible = true;
4413+
} else {
4414+
ElideConstructor();
4415+
return;
4416+
}
43874417
}
43884418

43894419
const RecordType *DestRecordType = DestType->getAs<RecordType>();
@@ -4428,6 +4458,12 @@ static void TryConstructorInitialization(Sema &S,
44284458
S, Kind.getLocation(), Args, CandidateSet, DestType, Ctors, Best,
44294459
CopyInitialization, AllowExplicit,
44304460
/*OnlyListConstructors=*/true, IsListInit, RequireActualConstructor);
4461+
4462+
if (CopyElisionPossible && Result == OR_No_Viable_Function) {
4463+
// No initializer list candidate
4464+
ElideConstructor();
4465+
return;
4466+
}
44314467
}
44324468

44334469
// C++11 [over.match.list]p1:
@@ -4709,9 +4745,9 @@ static void TryListInitialization(Sema &S,
47094745
return;
47104746
}
47114747

4712-
// C++11 [dcl.init.list]p3, per DR1467:
4713-
// - If T is a class type and the initializer list has a single element of
4714-
// type cv U, where U is T or a class derived from T, the object is
4748+
// C++11 [dcl.init.list]p3, per DR1467 and DR2137:
4749+
// - If T is an aggregate class and the initializer list has a single element
4750+
// of type cv U, where U is T or a class derived from T, the object is
47154751
// initialized from that element (by copy-initialization for
47164752
// copy-list-initialization, or by direct-initialization for
47174753
// direct-list-initialization).
@@ -4722,7 +4758,7 @@ static void TryListInitialization(Sema &S,
47224758
// - Otherwise, if T is an aggregate, [...] (continue below).
47234759
if (S.getLangOpts().CPlusPlus11 && InitList->getNumInits() == 1 &&
47244760
!IsDesignatedInit) {
4725-
if (DestType->isRecordType()) {
4761+
if (DestType->isRecordType() && DestType->isAggregateType()) {
47264762
QualType InitType = InitList->getInit(0)->getType();
47274763
if (S.Context.hasSameUnqualifiedType(InitType, DestType) ||
47284764
S.IsDerivedFrom(InitList->getBeginLoc(), InitType, DestType)) {

clang/lib/Sema/SemaOverload.cpp

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,19 +1618,36 @@ TryUserDefinedConversion(Sema &S, Expr *From, QualType ToType,
16181618
// called for those cases.
16191619
if (CXXConstructorDecl *Constructor
16201620
= dyn_cast<CXXConstructorDecl>(ICS.UserDefined.ConversionFunction)) {
1621-
QualType FromCanon
1622-
= S.Context.getCanonicalType(From->getType().getUnqualifiedType());
1621+
QualType FromType;
1622+
SourceLocation FromLoc;
1623+
// C++11 [over.ics.list]p6, per DR2137:
1624+
// C++17 [over.ics.list]p6:
1625+
// If C is not an initializer-list constructor and the initializer list
1626+
// has a single element of type cv U, where U is X or a class derived
1627+
// from X, the implicit conversion sequence has Exact Match rank if U is
1628+
// X, or Conversion rank if U is derived from X.
1629+
if (const auto *InitList = dyn_cast<InitListExpr>(From);
1630+
InitList && InitList->getNumInits() == 1 &&
1631+
!S.isInitListConstructor(Constructor)) {
1632+
const Expr *SingleInit = InitList->getInit(0);
1633+
FromType = SingleInit->getType();
1634+
FromLoc = SingleInit->getBeginLoc();
1635+
} else {
1636+
FromType = From->getType();
1637+
FromLoc = From->getBeginLoc();
1638+
}
1639+
QualType FromCanon =
1640+
S.Context.getCanonicalType(FromType.getUnqualifiedType());
16231641
QualType ToCanon
16241642
= S.Context.getCanonicalType(ToType).getUnqualifiedType();
1625-
if (Constructor->isCopyConstructor() &&
1626-
(FromCanon == ToCanon ||
1627-
S.IsDerivedFrom(From->getBeginLoc(), FromCanon, ToCanon))) {
1643+
if ((FromCanon == ToCanon ||
1644+
S.IsDerivedFrom(FromLoc, FromCanon, ToCanon))) {
16281645
// Turn this into a "standard" conversion sequence, so that it
16291646
// gets ranked with standard conversion sequences.
16301647
DeclAccessPair Found = ICS.UserDefined.FoundConversionFunction;
16311648
ICS.setStandard();
16321649
ICS.Standard.setAsIdentityConversion();
1633-
ICS.Standard.setFromType(From->getType());
1650+
ICS.Standard.setFromType(FromType);
16341651
ICS.Standard.setAllToTypes(ToType);
16351652
ICS.Standard.CopyConstructor = Constructor;
16361653
ICS.Standard.FoundCopyConstructor = Found;
@@ -5334,18 +5351,18 @@ TryListConversion(Sema &S, InitListExpr *From, QualType ToType,
53345351
IsDesignatedInit)
53355352
return Result;
53365353

5337-
// Per DR1467:
5338-
// If the parameter type is a class X and the initializer list has a single
5339-
// element of type cv U, where U is X or a class derived from X, the
5340-
// implicit conversion sequence is the one required to convert the element
5341-
// to the parameter type.
5354+
// Per DR1467 and DR2137:
5355+
// If the parameter type is an aggregate class X and the initializer list
5356+
// has a single element of type cv U, where U is X or a class derived from
5357+
// X, the implicit conversion sequence is the one required to convert the
5358+
// element to the parameter type.
53425359
//
53435360
// Otherwise, if the parameter type is a character array [... ]
53445361
// and the initializer list has a single element that is an
53455362
// appropriately-typed string literal (8.5.2 [dcl.init.string]), the
53465363
// implicit conversion sequence is the identity conversion.
53475364
if (From->getNumInits() == 1 && !IsDesignatedInit) {
5348-
if (ToType->isRecordType()) {
5365+
if (ToType->isRecordType() && ToType->isAggregateType()) {
53495366
QualType InitType = From->getInit(0)->getType();
53505367
if (S.Context.hasSameUnqualifiedType(InitType, ToType) ||
53515368
S.IsDerivedFrom(From->getBeginLoc(), InitType, ToType))

clang/test/CXX/drs/cwg14xx.cpp

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -505,16 +505,6 @@ namespace cwg1467 { // cwg1467: 3.7 c++11
505505
}
506506
} // nonaggregate
507507

508-
namespace SelfInitIsNotListInit {
509-
struct S {
510-
S();
511-
explicit S(S &);
512-
S(const S &);
513-
};
514-
S s1;
515-
S s2 = {s1}; // ok, not list-initialization so we pick the non-explicit constructor
516-
}
517-
518508
struct NestedInit { int a, b, c; };
519509
NestedInit ni[1] = {{NestedInit{1, 2, 3}}};
520510

clang/test/CXX/drs/cwg21xx.cpp

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,15 @@
1212
#endif
1313

1414
namespace std {
15-
struct type_info;
15+
typedef __SIZE_TYPE__ size_t;
16+
17+
template<typename E> struct initializer_list {
18+
const E *p; size_t n;
19+
initializer_list(const E *p, size_t n);
20+
initializer_list();
21+
};
22+
23+
struct type_info;
1624
}
1725

1826
namespace cwg2100 { // cwg2100: 12
@@ -136,6 +144,41 @@ namespace cwg2126 { // cwg2126: 12
136144
#endif
137145
}
138146

147+
namespace cwg2137 { // cwg2137: 20
148+
#if __cplusplus >= 201103L
149+
struct Q {
150+
Q();
151+
Q(Q&&);
152+
Q(std::initializer_list<Q>) = delete; // #cwg2137-Qcons
153+
};
154+
155+
Q x = Q { Q() };
156+
// since-cxx11-error@-1 {{call to deleted constructor of 'Q'}}
157+
// since-cxx11-note@#cwg2137-Qcons {{'Q' has been explicitly marked deleted here}}
158+
159+
int f(Q); // #cwg2137-f
160+
int y = f({ Q() });
161+
// since-cxx11-error@-1 {{call to deleted constructor of 'Q'}}
162+
// since-cxx11-note@#cwg2137-Qcons {{'Q' has been explicitly marked deleted here}}
163+
// since-cxx11-note@#cwg2137-f {{passing argument to parameter here}}
164+
165+
struct U {
166+
U();
167+
U(const U&);
168+
};
169+
170+
struct Derived : U {
171+
Derived();
172+
Derived(const Derived&);
173+
} d;
174+
175+
int g(Derived);
176+
int g(U(&&)[1]) = delete;
177+
178+
int z = g({ d });
179+
#endif
180+
}
181+
139182
namespace cwg2140 { // cwg2140: 9
140183
#if __cplusplus >= 201103L
141184
union U { int a; decltype(nullptr) b; };

clang/test/CXX/drs/cwg23xx.cpp

Lines changed: 99 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 cwg2303 { // cwg2303: 12
1121
template <typename... T>
@@ -94,6 +104,95 @@ struct Z : W,
94104
// cwg2331: na
95105
// cwg2335 is in cwg2335.cxx
96106

107+
namespace cwg2311 { // cwg2311 is open with no proposed resolution
108+
#if __cplusplus >= 201707L
109+
template<typename T>
110+
void test() {
111+
// Ensure none of these try to call a move constructor.
112+
T a = T{T(0)};
113+
T b{T(0)};
114+
auto c{T(0)};
115+
T d = {T(0)};
116+
auto e = {T(0)};
117+
#if __cplusplus >= 202302L
118+
auto f = auto{T(0)};
119+
#endif
120+
void(*fn)(T);
121+
fn({T(0)});
122+
}
123+
124+
struct NonMovable {
125+
NonMovable(int);
126+
NonMovable(NonMovable&&) = delete;
127+
};
128+
struct NonMovableNonApplicableIList {
129+
NonMovableNonApplicableIList(int);
130+
NonMovableNonApplicableIList(NonMovableNonApplicableIList&&) = delete;
131+
NonMovableNonApplicableIList(std::initializer_list<int>);
132+
};
133+
struct ExplicitMovable {
134+
ExplicitMovable(int);
135+
explicit ExplicitMovable(ExplicitMovable&&);
136+
};
137+
struct ExplicitNonMovable {
138+
ExplicitNonMovable(int);
139+
explicit ExplicitNonMovable(ExplicitNonMovable&&) = delete;
140+
};
141+
struct ExplicitNonMovableNonApplicableIList {
142+
ExplicitNonMovableNonApplicableIList(int);
143+
explicit ExplicitNonMovableNonApplicableIList(ExplicitNonMovableNonApplicableIList&&) = delete;
144+
ExplicitNonMovableNonApplicableIList(std::initializer_list<int>);
145+
};
146+
struct CopyOnly {
147+
CopyOnly(int);
148+
CopyOnly(const CopyOnly&);
149+
CopyOnly(CopyOnly&&) = delete;
150+
};
151+
struct ExplicitCopyOnly {
152+
ExplicitCopyOnly(int);
153+
explicit ExplicitCopyOnly(const ExplicitCopyOnly&);
154+
explicit ExplicitCopyOnly(ExplicitCopyOnly&&) = delete;
155+
};
156+
157+
template void test<NonMovable>();
158+
template void test<NonMovableNonApplicableIList>();
159+
template void test<ExplicitMovable>();
160+
template void test<ExplicitNonMovable>();
161+
template void test<ExplicitNonMovableNonApplicableIList>();
162+
template void test<CopyOnly>();
163+
template void test<ExplicitCopyOnly>();
164+
165+
struct any {
166+
template<typename T>
167+
any(T&&);
168+
};
169+
170+
template<typename T>
171+
struct X {
172+
X();
173+
X(T) = delete; // #cwg2311-X
174+
};
175+
176+
X<std::initializer_list<any>> x{ X<std::initializer_list<any>>() };
177+
// since-cxx17-error@-1 {{call to deleted constructor of 'X<std::initializer_list<any>>'}}
178+
// since-cxx17-note@#cwg2311-X {{'X' has been explicitly marked deleted here}}
179+
180+
// Per the currently implemented resolution, this does not apply to std::initializer_list.
181+
// An initializer list initialized from `{ e }` always has exactly one element constructed
182+
// from `e`, where previously that could have been a copy of an init list or `e.operator std::initializer_list()`
183+
struct InitListCtor {
184+
InitListCtor(int);
185+
InitListCtor(InitListCtor&&) = delete;
186+
InitListCtor(std::initializer_list<InitListCtor>) = delete; // #cwg2311-InitListCtor
187+
};
188+
189+
std::initializer_list<InitListCtor> i;
190+
auto j = std::initializer_list<InitListCtor>{ i };
191+
// since-cxx17-error@-1 {{conversion function from 'std::initializer_list<InitListCtor>' to 'const cwg2311::InitListCtor' invokes a deleted function}}
192+
// since-cxx17-note@#cwg2311-InitListCtor {{'InitListCtor' has been explicitly marked deleted here}}
193+
#endif
194+
}
195+
97196
#if __cplusplus >= 201103L
98197
namespace cwg2338 { // cwg2338: 12
99198
namespace B {

clang/test/SemaCXX/cxx1z-class-template-argument-deduction.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@ template<typename T> constexpr bool has_type(T&) { return true; }
1919

2020
std::initializer_list il1 = {1, 2, 3, 4, 5};
2121
auto il2 = std::initializer_list{1, 2, 3, 4};
22-
auto il3 = std::initializer_list{il1};
22+
auto il3 = std::initializer_list(il1);
2323
auto il4 = std::initializer_list{il1, il1, il1};
2424
static_assert(has_type<std::initializer_list<int>>(il1));
2525
static_assert(has_type<std::initializer_list<int>>(il2));
2626
static_assert(has_type<std::initializer_list<int>>(il3));
2727
static_assert(has_type<std::initializer_list<std::initializer_list<int>>>(il4));
2828

29+
auto il5 = std::initializer_list{il1};
30+
// expected-error@-1 {{no viable conversion from 'std::initializer_list<int>' to 'const int'}}
31+
2932
template<typename T> struct vector {
3033
template<typename Iter> vector(Iter, Iter);
3134
vector(std::initializer_list<T>);

0 commit comments

Comments
 (0)