Skip to content

Commit a760df3

Browse files
authored
[Clang] Implement CWG2137 (list-initialization from objects of the same type) (#94355)
[CWG2137](https://cplusplus.github.io/CWG/issues/2137.html) This was previously implemented and then reverted in Clang 18 as #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 [...]
1 parent b2e69f5 commit a760df3

9 files changed

+315
-35
lines changed

clang/docs/ReleaseNotes.rst

+10
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ C++2c Feature Support
101101
Resolutions to C++ Defect Reports
102102
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
103103

104+
- Allow calling initializer list constructors from initializer lists with
105+
a single element of the same type instead of always copying.
106+
(`CWG2137: List-initialization from object of same type <https://cplusplus.github.io/CWG/issues/2137.html>`)
107+
108+
- Speculative resolution for CWG2311 implemented so that the implementation of CWG2137 doesn't remove
109+
previous cases where guaranteed copy elision was done. Given a prvalue ``e`` of class type
110+
``T``, ``T{e}`` will try to resolve an initializer list constructor and will use it if successful.
111+
Otherwise, if there is no initializer list constructor, the copy will be elided as if it was ``T(e)``.
112+
(`CWG2311: Missed case for guaranteed copy elision <https://cplusplus.github.io/CWG/issues/2311.html>`)
113+
104114
C Language Changes
105115
------------------
106116

clang/lib/Sema/SemaInit.cpp

+46-10
Original file line numberDiff line numberDiff line change
@@ -4340,7 +4340,7 @@ static OverloadingResult ResolveConstructorOverload(
43404340
/// \param IsListInit Is this list-initialization?
43414341
/// \param IsInitListCopy Is this non-list-initialization resulting from a
43424342
/// list-initialization from {x} where x is the same
4343-
/// type as the entity?
4343+
/// aggregate type as the entity?
43444344
static void TryConstructorInitialization(Sema &S,
43454345
const InitializedEntity &Entity,
43464346
const InitializationKind &Kind,
@@ -4370,6 +4370,14 @@ static void TryConstructorInitialization(Sema &S,
43704370
Entity.getKind() !=
43714371
InitializedEntity::EK_LambdaToBlockConversionBlockElement);
43724372

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

43924422
const RecordType *DestRecordType = DestType->getAs<RecordType>();
@@ -4431,6 +4461,12 @@ static void TryConstructorInitialization(Sema &S,
44314461
S, Kind.getLocation(), Args, CandidateSet, DestType, Ctors, Best,
44324462
CopyInitialization, AllowExplicit,
44334463
/*OnlyListConstructors=*/true, IsListInit, RequireActualConstructor);
4464+
4465+
if (CopyElisionPossible && Result == OR_No_Viable_Function) {
4466+
// No initializer list candidate
4467+
ElideConstructor();
4468+
return;
4469+
}
44344470
}
44354471

44364472
// C++11 [over.match.list]p1:
@@ -4712,9 +4748,9 @@ static void TryListInitialization(Sema &S,
47124748
return;
47134749
}
47144750

4715-
// C++11 [dcl.init.list]p3, per DR1467:
4716-
// - If T is a class type and the initializer list has a single element of
4717-
// type cv U, where U is T or a class derived from T, the object is
4751+
// C++11 [dcl.init.list]p3, per DR1467 and DR2137:
4752+
// - If T is an aggregate class and the initializer list has a single element
4753+
// of type cv U, where U is T or a class derived from T, the object is
47184754
// initialized from that element (by copy-initialization for
47194755
// copy-list-initialization, or by direct-initialization for
47204756
// direct-list-initialization).
@@ -4725,7 +4761,7 @@ static void TryListInitialization(Sema &S,
47254761
// - Otherwise, if T is an aggregate, [...] (continue below).
47264762
if (S.getLangOpts().CPlusPlus11 && InitList->getNumInits() == 1 &&
47274763
!IsDesignatedInit) {
4728-
if (DestType->isRecordType()) {
4764+
if (DestType->isRecordType() && DestType->isAggregateType()) {
47294765
QualType InitType = InitList->getInit(0)->getType();
47304766
if (S.Context.hasSameUnqualifiedType(InitType, DestType) ||
47314767
S.IsDerivedFrom(InitList->getBeginLoc(), InitType, DestType)) {

clang/lib/Sema/SemaOverload.cpp

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

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

clang/test/CXX/drs/cwg14xx.cpp

-10
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

+44-1
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

+99
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

+4-1
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)