Skip to content

Commit

Permalink
[SemaCXX] Allow some copy elision of T{ T_prvalue } if an initializer…
Browse files Browse the repository at this point in the history
…-list constructor isn't used (CWG2311)

Differential Revision: https://reviews.llvm.org/D156032
  • Loading branch information
MitalAshok committed Jan 19, 2024
1 parent 644ec10 commit a3b62a4
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 8 deletions.
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Implemented `CWG2137 <https://wg21.link/CWG2137>`_ which allows
list-initialization from objects of the same type.
- Implemented `CWG2311 <https://wg21.link/CWG2311>`_: given a prvalue ``e`` of object type
``T``, ``T{e}`` will try to resolve an initializer list constructor and will use it if successful (CWG2137).
Otherwise, if there is no initializer list constructor, the copy will be elided as if it was ``T(e)``.

- Implemented `CWG2598 <https://wg21.link/CWG2598>`_ and `CWG2096 <https://wg21.link/CWG2096>`_,
making unions (that have either no members or at least one literal member) literal types.
Expand Down
34 changes: 27 additions & 7 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4230,6 +4230,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 @@ -4240,13 +4248,19 @@ static void TryConstructorInitialization(Sema &S,
// ObjC++: Lambda captured by the block in the lambda to block conversion
// should avoid copy elision.
if (S.getLangOpts().CPlusPlus17 && !RequireActualConstructor &&
Args.size() == 1 && Args[0]->isPRValue() &&
S.Context.hasSameUnqualifiedType(Args[0]->getType(), DestType)) {
// Convert qualifications if necessary.
Sequence.AddQualificationConversionStep(DestType, VK_PRValue);
if (ILE)
Sequence.RewrapReferenceInitList(DestType, ILE);
return;
UnwrappedArgs.size() == 1 && UnwrappedArgs[0]->isPRValue() &&
S.Context.hasSameUnqualifiedType(UnwrappedArgs[0]->getType(), DestType)) {
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.)
assert(!IsInitListCopy &&
"IsInitListCopy only possible with aggregate types");
CopyElisionPossible = true;
} else {
ElideConstructor();
return;
}
}

const RecordType *DestRecordType = DestType->getAs<RecordType>();
Expand Down Expand Up @@ -4291,6 +4305,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
85 changes: 85 additions & 0 deletions clang/test/CXX/drs/dr23xx.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 dr2303 { // dr2303: 12
template <typename... T>
Expand Down Expand Up @@ -47,6 +57,81 @@ void g() {
} //namespace dr2303
#endif

namespace dr2311 { // dr2311: 18 open
#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; // #dr2311-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@#dr2311-X {{'X' has been explicitly marked deleted here}}
#endif
}

// dr2331: na
// dr2335 is in dr2335.cxx

Expand Down
2 changes: 1 addition & 1 deletion clang/www/cxx_dr_status.html
Original file line number Diff line number Diff line change
Expand Up @@ -13674,7 +13674,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2311.html">2311</a></td>
<td>open</td>
<td>Missed case for guaranteed copy elision</td>
<td align="center">Not resolved</td>
<td class="unreleased" align="center">Clang 18</td>
</tr>
<tr id="2312">
<td><a href="https://cplusplus.github.io/CWG/issues/2312.html">2312</a></td>
Expand Down

0 comments on commit a3b62a4

Please sign in to comment.