Skip to content

Commit

Permalink
[Clang] Implement CWG2137 (list-initialization from objects of the sa…
Browse files Browse the repository at this point in the history
…me 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 [...]
  • Loading branch information
MitalAshok authored Aug 8, 2024
1 parent b2e69f5 commit a760df3
Show file tree
Hide file tree
Showing 9 changed files with 315 additions and 35 deletions.
10 changes: 10 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ C++2c Feature Support
Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

- Allow calling initializer list constructors from initializer lists with
a single element of the same type instead of always copying.
(`CWG2137: List-initialization from object of same type <https://cplusplus.github.io/CWG/issues/2137.html>`)

- Speculative resolution for CWG2311 implemented so that the implementation of CWG2137 doesn't remove
previous cases where guaranteed copy elision was done. Given a prvalue ``e`` of class type
``T``, ``T{e}`` will try to resolve an initializer list constructor and will use it if successful.
Otherwise, if there is no initializer list constructor, the copy will be elided as if it was ``T(e)``.
(`CWG2311: Missed case for guaranteed copy elision <https://cplusplus.github.io/CWG/issues/2311.html>`)

C Language Changes
------------------

Expand Down
56 changes: 46 additions & 10 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4340,7 +4340,7 @@ static OverloadingResult ResolveConstructorOverload(
/// \param IsListInit Is this list-initialization?
/// \param IsInitListCopy Is this non-list-initialization resulting from a
/// list-initialization from {x} where x is the same
/// type as the entity?
/// aggregate type as the entity?
static void TryConstructorInitialization(Sema &S,
const InitializedEntity &Entity,
const InitializationKind &Kind,
Expand Down Expand Up @@ -4370,6 +4370,14 @@ static void TryConstructorInitialization(Sema &S,
Entity.getKind() !=
InitializedEntity::EK_LambdaToBlockConversionBlockElement);

bool CopyElisionPossible = false;
auto ElideConstructor = [&] {
// Convert qualifications if necessary.
Sequence.AddQualificationConversionStep(DestType, VK_PRValue);
if (ILE)
Sequence.RewrapReferenceInitList(DestType, ILE);
};

// C++17 [dcl.init]p17:
// - If the initializer expression is a prvalue and the cv-unqualified
// version of the source type is the same class as the class of the
Expand All @@ -4382,11 +4390,33 @@ static void TryConstructorInitialization(Sema &S,
if (S.getLangOpts().CPlusPlus17 && !RequireActualConstructor &&
UnwrappedArgs.size() == 1 && UnwrappedArgs[0]->isPRValue() &&
S.Context.hasSameUnqualifiedType(UnwrappedArgs[0]->getType(), DestType)) {
// Convert qualifications if necessary.
Sequence.AddQualificationConversionStep(DestType, VK_PRValue);
if (ILE)
Sequence.RewrapReferenceInitList(DestType, ILE);
return;
if (ILE && !DestType->isAggregateType()) {
// CWG2311: T{ prvalue_of_type_T } is not eligible for copy elision
// Make this an elision if this won't call an initializer-list
// constructor. (Always on an aggregate type or check constructors first.)

// This effectively makes our resolution as follows. The parts in angle
// brackets are additions.
// C++17 [over.match.list]p(1.2):
// - If no viable initializer-list constructor is found <and the
// initializer list does not consist of exactly a single element with
// the same cv-unqualified class type as T>, [...]
// C++17 [dcl.init.list]p(3.6):
// - Otherwise, if T is a class type, constructors are considered. The
// applicable constructors are enumerated and the best one is chosen
// through overload resolution. <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, >
// if a narrowing conversion [...]
assert(!IsInitListCopy &&
"IsInitListCopy only possible with aggregate types");
CopyElisionPossible = true;
} else {
ElideConstructor();
return;
}
}

const RecordType *DestRecordType = DestType->getAs<RecordType>();
Expand Down Expand Up @@ -4431,6 +4461,12 @@ static void TryConstructorInitialization(Sema &S,
S, Kind.getLocation(), Args, CandidateSet, DestType, Ctors, Best,
CopyInitialization, AllowExplicit,
/*OnlyListConstructors=*/true, IsListInit, RequireActualConstructor);

if (CopyElisionPossible && Result == OR_No_Viable_Function) {
// No initializer list candidate
ElideConstructor();
return;
}
}

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

// C++11 [dcl.init.list]p3, per DR1467:
// - If T is a class type and the initializer list has a single element of
// type cv U, where U is T or a class derived from T, the object is
// C++11 [dcl.init.list]p3, per DR1467 and DR2137:
// - If T is an aggregate class and the initializer list has a single element
// of type cv U, where U is T or a class derived from T, the object is
// initialized from that element (by copy-initialization for
// copy-list-initialization, or by direct-initialization for
// direct-list-initialization).
Expand All @@ -4725,7 +4761,7 @@ static void TryListInitialization(Sema &S,
// - Otherwise, if T is an aggregate, [...] (continue below).
if (S.getLangOpts().CPlusPlus11 && InitList->getNumInits() == 1 &&
!IsDesignatedInit) {
if (DestType->isRecordType()) {
if (DestType->isRecordType() && DestType->isAggregateType()) {
QualType InitType = InitList->getInit(0)->getType();
if (S.Context.hasSameUnqualifiedType(InitType, DestType) ||
S.IsDerivedFrom(InitList->getBeginLoc(), InitType, DestType)) {
Expand Down
41 changes: 29 additions & 12 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1619,19 +1619,36 @@ TryUserDefinedConversion(Sema &S, Expr *From, QualType ToType,
// called for those cases.
if (CXXConstructorDecl *Constructor
= dyn_cast<CXXConstructorDecl>(ICS.UserDefined.ConversionFunction)) {
QualType FromCanon
= S.Context.getCanonicalType(From->getType().getUnqualifiedType());
QualType FromType;
SourceLocation FromLoc;
// C++11 [over.ics.list]p6, per DR2137:
// C++17 [over.ics.list]p6:
// If C is not an initializer-list constructor and the initializer list
// has a single element of type cv U, where U is X or a class derived
// from X, the implicit conversion sequence has Exact Match rank if U is
// X, or Conversion rank if U is derived from X.
if (const auto *InitList = dyn_cast<InitListExpr>(From);
InitList && InitList->getNumInits() == 1 &&
!S.isInitListConstructor(Constructor)) {
const Expr *SingleInit = InitList->getInit(0);
FromType = SingleInit->getType();
FromLoc = SingleInit->getBeginLoc();
} else {
FromType = From->getType();
FromLoc = From->getBeginLoc();
}
QualType FromCanon =
S.Context.getCanonicalType(FromType.getUnqualifiedType());
QualType ToCanon
= S.Context.getCanonicalType(ToType).getUnqualifiedType();
if (Constructor->isCopyConstructor() &&
(FromCanon == ToCanon ||
S.IsDerivedFrom(From->getBeginLoc(), FromCanon, ToCanon))) {
if ((FromCanon == ToCanon ||
S.IsDerivedFrom(FromLoc, FromCanon, ToCanon))) {
// Turn this into a "standard" conversion sequence, so that it
// gets ranked with standard conversion sequences.
DeclAccessPair Found = ICS.UserDefined.FoundConversionFunction;
ICS.setStandard();
ICS.Standard.setAsIdentityConversion();
ICS.Standard.setFromType(From->getType());
ICS.Standard.setFromType(FromType);
ICS.Standard.setAllToTypes(ToType);
ICS.Standard.CopyConstructor = Constructor;
ICS.Standard.FoundCopyConstructor = Found;
Expand Down Expand Up @@ -5335,18 +5352,18 @@ TryListConversion(Sema &S, InitListExpr *From, QualType ToType,
IsDesignatedInit)
return Result;

// Per DR1467:
// If the parameter type is a class X and the initializer list has a single
// element of type cv U, where U is X or a class derived from X, the
// implicit conversion sequence is the one required to convert the element
// to the parameter type.
// Per DR1467 and DR2137:
// If the parameter type is an aggregate class X and the initializer list
// has a single element of type cv U, where U is X or a class derived from
// X, the implicit conversion sequence is the one required to convert the
// element to the parameter type.
//
// Otherwise, if the parameter type is a character array [... ]
// and the initializer list has a single element that is an
// appropriately-typed string literal (8.5.2 [dcl.init.string]), the
// implicit conversion sequence is the identity conversion.
if (From->getNumInits() == 1 && !IsDesignatedInit) {
if (ToType->isRecordType()) {
if (ToType->isRecordType() && ToType->isAggregateType()) {
QualType InitType = From->getInit(0)->getType();
if (S.Context.hasSameUnqualifiedType(InitType, ToType) ||
S.IsDerivedFrom(From->getBeginLoc(), InitType, ToType))
Expand Down
10 changes: 0 additions & 10 deletions clang/test/CXX/drs/cwg14xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -505,16 +505,6 @@ namespace cwg1467 { // cwg1467: 3.7 c++11
}
} // nonaggregate

namespace SelfInitIsNotListInit {
struct S {
S();
explicit S(S &);
S(const S &);
};
S s1;
S s2 = {s1}; // ok, not list-initialization so we pick the non-explicit constructor
}

struct NestedInit { int a, b, c; };
NestedInit ni[1] = {{NestedInit{1, 2, 3}}};

Expand Down
45 changes: 44 additions & 1 deletion clang/test/CXX/drs/cwg21xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@
#endif

namespace std {
struct type_info;
typedef __SIZE_TYPE__ size_t;

template<typename E> struct initializer_list {
const E *p; size_t n;
initializer_list(const E *p, size_t n);
initializer_list();
};

struct type_info;
}

namespace cwg2100 { // cwg2100: 12
Expand Down Expand Up @@ -136,6 +144,41 @@ namespace cwg2126 { // cwg2126: 12
#endif
}

namespace cwg2137 { // cwg2137: 20
#if __cplusplus >= 201103L
struct Q {
Q();
Q(Q&&);
Q(std::initializer_list<Q>) = delete; // #cwg2137-Qcons
};

Q x = Q { Q() };
// since-cxx11-error@-1 {{call to deleted constructor of 'Q'}}
// since-cxx11-note@#cwg2137-Qcons {{'Q' has been explicitly marked deleted here}}

int f(Q); // #cwg2137-f
int y = f({ Q() });
// since-cxx11-error@-1 {{call to deleted constructor of 'Q'}}
// since-cxx11-note@#cwg2137-Qcons {{'Q' has been explicitly marked deleted here}}
// since-cxx11-note@#cwg2137-f {{passing argument to parameter here}}

struct U {
U();
U(const U&);
};

struct Derived : U {
Derived();
Derived(const Derived&);
} d;

int g(Derived);
int g(U(&&)[1]) = delete;

int z = g({ d });
#endif
}

namespace cwg2140 { // cwg2140: 9
#if __cplusplus >= 201103L
union U { int a; decltype(nullptr) b; };
Expand Down
99 changes: 99 additions & 0 deletions clang/test/CXX/drs/cwg23xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
// 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
// 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

namespace std {
__extension__ typedef __SIZE_TYPE__ size_t;

template<typename E> struct initializer_list {
const E *p; size_t n;
initializer_list(const E *p, size_t n);
initializer_list();
};
}

#if __cplusplus >= 201103L
namespace cwg2303 { // cwg2303: 12
template <typename... T>
Expand Down Expand Up @@ -94,6 +104,95 @@ struct Z : W,
// cwg2331: na
// cwg2335 is in cwg2335.cxx

namespace cwg2311 { // cwg2311 is open with no proposed resolution
#if __cplusplus >= 201707L
template<typename T>
void test() {
// Ensure none of these try to call a move constructor.
T a = T{T(0)};
T b{T(0)};
auto c{T(0)};
T d = {T(0)};
auto e = {T(0)};
#if __cplusplus >= 202302L
auto f = auto{T(0)};
#endif
void(*fn)(T);
fn({T(0)});
}

struct NonMovable {
NonMovable(int);
NonMovable(NonMovable&&) = delete;
};
struct NonMovableNonApplicableIList {
NonMovableNonApplicableIList(int);
NonMovableNonApplicableIList(NonMovableNonApplicableIList&&) = delete;
NonMovableNonApplicableIList(std::initializer_list<int>);
};
struct ExplicitMovable {
ExplicitMovable(int);
explicit ExplicitMovable(ExplicitMovable&&);
};
struct ExplicitNonMovable {
ExplicitNonMovable(int);
explicit ExplicitNonMovable(ExplicitNonMovable&&) = delete;
};
struct ExplicitNonMovableNonApplicableIList {
ExplicitNonMovableNonApplicableIList(int);
explicit ExplicitNonMovableNonApplicableIList(ExplicitNonMovableNonApplicableIList&&) = delete;
ExplicitNonMovableNonApplicableIList(std::initializer_list<int>);
};
struct CopyOnly {
CopyOnly(int);
CopyOnly(const CopyOnly&);
CopyOnly(CopyOnly&&) = delete;
};
struct ExplicitCopyOnly {
ExplicitCopyOnly(int);
explicit ExplicitCopyOnly(const ExplicitCopyOnly&);
explicit ExplicitCopyOnly(ExplicitCopyOnly&&) = delete;
};

template void test<NonMovable>();
template void test<NonMovableNonApplicableIList>();
template void test<ExplicitMovable>();
template void test<ExplicitNonMovable>();
template void test<ExplicitNonMovableNonApplicableIList>();
template void test<CopyOnly>();
template void test<ExplicitCopyOnly>();

struct any {
template<typename T>
any(T&&);
};

template<typename T>
struct X {
X();
X(T) = delete; // #cwg2311-X
};

X<std::initializer_list<any>> x{ X<std::initializer_list<any>>() };
// since-cxx17-error@-1 {{call to deleted constructor of 'X<std::initializer_list<any>>'}}
// since-cxx17-note@#cwg2311-X {{'X' has been explicitly marked deleted here}}

// Per the currently implemented resolution, this does not apply to std::initializer_list.
// An initializer list initialized from `{ e }` always has exactly one element constructed
// from `e`, where previously that could have been a copy of an init list or `e.operator std::initializer_list()`
struct InitListCtor {
InitListCtor(int);
InitListCtor(InitListCtor&&) = delete;
InitListCtor(std::initializer_list<InitListCtor>) = delete; // #cwg2311-InitListCtor
};

std::initializer_list<InitListCtor> i;
auto j = std::initializer_list<InitListCtor>{ i };
// since-cxx17-error@-1 {{conversion function from 'std::initializer_list<InitListCtor>' to 'const cwg2311::InitListCtor' invokes a deleted function}}
// since-cxx17-note@#cwg2311-InitListCtor {{'InitListCtor' has been explicitly marked deleted here}}
#endif
}

#if __cplusplus >= 201103L
namespace cwg2338 { // cwg2338: 12
namespace B {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ template<typename T> constexpr bool has_type(T&) { return true; }

std::initializer_list il1 = {1, 2, 3, 4, 5};
auto il2 = std::initializer_list{1, 2, 3, 4};
auto il3 = std::initializer_list{il1};
auto il3 = std::initializer_list(il1);
auto il4 = std::initializer_list{il1, il1, il1};
static_assert(has_type<std::initializer_list<int>>(il1));
static_assert(has_type<std::initializer_list<int>>(il2));
static_assert(has_type<std::initializer_list<int>>(il3));
static_assert(has_type<std::initializer_list<std::initializer_list<int>>>(il4));

auto il5 = std::initializer_list{il1};
// expected-error@-1 {{no viable conversion from 'std::initializer_list<int>' to 'const int'}}

template<typename T> struct vector {
template<typename Iter> vector(Iter, Iter);
vector(std::initializer_list<T>);
Expand Down
Loading

0 comments on commit a760df3

Please sign in to comment.