[clang] [libcxx] [Clang] Implement CWG2137 (list-initialization from objects of the same type) (PR #94355)

Mital Ashok via cfe-commits cfe-commits at lists.llvm.org
Tue Jun 4 06:47:46 PDT 2024


https://github.com/MitalAshok created https://github.com/llvm/llvm-project/pull/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 [...]



>From ac803f979f2779da35a006988d2d42cdabbad252 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Sat, 22 Jul 2023 20:07:00 +0100
Subject: [PATCH 1/5] [SemaCXX] Implement CWG2137 (list-initialization from
 objects of the same type)

Differential Revision: https://reviews.llvm.org/D156032

---

This is a cherry-pick from https://github.com/llvm/llvm-project/pull/77768/commits/644ec10fc357f70ca8af94ae6544e9631021eb5e
---
 clang/docs/ReleaseNotes.rst                   |  3 ++
 clang/lib/Sema/SemaInit.cpp                   | 14 +++---
 clang/lib/Sema/SemaOverload.cpp               | 38 +++++++++++-----
 clang/test/CXX/drs/cwg14xx.cpp                | 10 -----
 clang/test/CXX/drs/cwg21xx.cpp                | 45 +++++++++++++++++++
 clang/www/cxx_dr_status.html                  |  2 +-
 .../pairs.pair/ctor.pair_U_V_move.pass.cpp    | 21 ++++++++-
 7 files changed, 104 insertions(+), 29 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 99580c0d28a4f..818fa160ef984 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -254,6 +254,9 @@ Resolutions to C++ Defect Reports
 - Clang now requires a template argument list after a template keyword.
   (`CWG96: Syntactic disambiguation using the template keyword <https://cplusplus.github.io/CWG/issues/96.html>`_).
 
+- Implemented `CWG2137 <https://wg21.link/CWG2137>`_ which allows
+  list-initialization from objects of the same type.
+
 C Language Changes
 ------------------
 
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 79bdc8e9f8783..6aa0ebeeaa11f 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -4231,7 +4231,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,
@@ -4271,8 +4271,8 @@ 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 &&
-      UnwrappedArgs.size() == 1 && UnwrappedArgs[0]->isPRValue() &&
-      S.Context.hasSameUnqualifiedType(UnwrappedArgs[0]->getType(), DestType)) {
+      Args.size() == 1 && Args[0]->isPRValue() &&
+      S.Context.hasSameUnqualifiedType(Args[0]->getType(), DestType)) {
     // Convert qualifications if necessary.
     Sequence.AddQualificationConversionStep(DestType, VK_PRValue);
     if (ILE)
@@ -4603,9 +4603,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).
@@ -4616,7 +4616,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)) {
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 6c4ce1022ae27..b6d6d9d1ef3af 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1628,19 +1628,37 @@ 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))) {
+           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;
@@ -5431,18 +5449,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))
diff --git a/clang/test/CXX/drs/cwg14xx.cpp b/clang/test/CXX/drs/cwg14xx.cpp
index f01d96ad47f3e..a23ac74443633 100644
--- a/clang/test/CXX/drs/cwg14xx.cpp
+++ b/clang/test/CXX/drs/cwg14xx.cpp
@@ -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}}};
 
diff --git a/clang/test/CXX/drs/cwg21xx.cpp b/clang/test/CXX/drs/cwg21xx.cpp
index 082deb42e4fa0..2dee8fb90336c 100644
--- a/clang/test/CXX/drs/cwg21xx.cpp
+++ b/clang/test/CXX/drs/cwg21xx.cpp
@@ -11,6 +11,16 @@
 // cxx98-error at -1 {{variadic macros are a C99 feature}}
 #endif
 
+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();
+  };
+}
+
 namespace cwg2100 { // cwg2100: 12
   template<const int *P, bool = true> struct X {};
   template<typename T> struct A {
@@ -132,6 +142,41 @@ namespace cwg2126 { // cwg2126: 12
 #endif
 }
 
+namespace dr2137 { // dr2137: 18
+#if __cplusplus >= 201103L
+  struct Q {
+    Q();
+    Q(Q&&);
+    Q(std::initializer_list<Q>) = delete; // #dr2137-Qcons
+  };
+
+  Q x = Q { Q() };
+  // since-cxx11-error at -1 {{call to deleted constructor of 'Q'}}
+  //   since-cxx11-note@#dr2137-Qcons {{'Q' has been explicitly marked deleted here}}
+
+  int f(Q); // #dr2137-f
+  int y = f({ Q() });
+  // since-cxx11-error at -1 {{call to deleted constructor of 'Q'}}
+  //   since-cxx11-note@#dr2137-Qcons {{'Q' has been explicitly marked deleted here}}
+  //   since-cxx11-note@#dr2137-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; };
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 4d94ac5a1ac1d..a065e3d54b748 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -12630,7 +12630,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
     <td><a href="https://cplusplus.github.io/CWG/issues/2137.html">2137</a></td>
     <td>CD4</td>
     <td>List-initialization from object of same type</td>
-    <td class="unknown" align="center">Unknown</td>
+    <td class="unreleased" align="center">Clang 18</td>
   </tr>
   <tr id="2138">
     <td><a href="https://cplusplus.github.io/CWG/issues/2138.html">2138</a></td>
diff --git a/libcxx/test/std/utilities/utility/pairs/pairs.pair/ctor.pair_U_V_move.pass.cpp b/libcxx/test/std/utilities/utility/pairs/pairs.pair/ctor.pair_U_V_move.pass.cpp
index 3b2d093eb34d4..30fdb19fd3aeb 100644
--- a/libcxx/test/std/utilities/utility/pairs/pairs.pair/ctor.pair_U_V_move.pass.cpp
+++ b/libcxx/test/std/utilities/utility/pairs/pairs.pair/ctor.pair_U_V_move.pass.cpp
@@ -121,7 +121,26 @@ int main(int, char**)
         test_pair_rv<CopyOnly, CopyOnly&>();
         test_pair_rv<CopyOnly, CopyOnly&&>();
 
-        test_pair_rv<ExplicitTypes::CopyOnly, ExplicitTypes::CopyOnly>();
+        /* For ExplicitTypes::CopyOnly, two of the viable candidates for initializing from a non-const xvalue are:
+         *   pair(const pair&);  // (defaulted copy constructor)
+         *   template<class U1, class U2> explicit pair(const pair<U1, U2>&&); [U1 = ExplicitTypes::CopyOnly, U2 = int]
+         * This results in diverging behavior for test_convertible which uses copy-list-initialization
+         * Prior to CWG2137, this would have selected the first (non-explicit) ctor as explicit ctors would not be considered
+         * Afterwards, it should select the second since it is a better match, and then failed because it is explicit
+         *
+         * This may change with future defect reports, and some compilers only have partial support for CWG2137,
+         * so use std::is_convertible directly to avoid a copy-list-initialization
+         */
+        {
+          using P1  = std::pair<ExplicitTypes::CopyOnly, int>;
+          using P2  = std::pair<int, ExplicitTypes::CopyOnly>;
+          using UP1 = std::pair<ExplicitTypes::CopyOnly, int>&&;
+          using UP2 = std::pair<int, ExplicitTypes::CopyOnly>&&;
+          static_assert(std::is_constructible<P1, UP1>::value, "");
+          static_assert(std::is_convertible<P1, UP1>::value, "");
+          static_assert(std::is_constructible<P2, UP2>::value, "");
+          static_assert(std::is_convertible<P2, UP2>::value, "");
+        }
         test_pair_rv<ExplicitTypes::CopyOnly, ExplicitTypes::CopyOnly&, true, false>();
         test_pair_rv<ExplicitTypes::CopyOnly, ExplicitTypes::CopyOnly&&, true, false>();
 

>From 1e13e1a459e449e53c44edf2ba35d7d64ea60bd1 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Tue, 4 Jun 2024 11:59:32 +0100
Subject: [PATCH 2/5] [NFC] dr -> cwg, reword ReleaseNotes

---
 clang/docs/ReleaseNotes.rst    |  5 +++--
 clang/test/CXX/drs/cwg21xx.cpp | 14 +++++++-------
 clang/www/cxx_dr_status.html   |  2 +-
 3 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 818fa160ef984..83ab694fc77cb 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -254,8 +254,9 @@ Resolutions to C++ Defect Reports
 - Clang now requires a template argument list after a template keyword.
   (`CWG96: Syntactic disambiguation using the template keyword <https://cplusplus.github.io/CWG/issues/96.html>`_).
 
-- Implemented `CWG2137 <https://wg21.link/CWG2137>`_ which allows
-  list-initialization from objects of the same type.
+- 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>`)
 
 C Language Changes
 ------------------
diff --git a/clang/test/CXX/drs/cwg21xx.cpp b/clang/test/CXX/drs/cwg21xx.cpp
index 2dee8fb90336c..48588cc08b2ac 100644
--- a/clang/test/CXX/drs/cwg21xx.cpp
+++ b/clang/test/CXX/drs/cwg21xx.cpp
@@ -12,7 +12,7 @@
 #endif
 
 namespace std {
-  __extension__ typedef __SIZE_TYPE__ size_t;
+  typedef __SIZE_TYPE__ size_t;
 
   template<typename E> struct initializer_list {
     const E *p; size_t n;
@@ -142,23 +142,23 @@ namespace cwg2126 { // cwg2126: 12
 #endif
 }
 
-namespace dr2137 { // dr2137: 18
+namespace cwg2137 { // cwg2137: 19
 #if __cplusplus >= 201103L
   struct Q {
     Q();
     Q(Q&&);
-    Q(std::initializer_list<Q>) = delete; // #dr2137-Qcons
+    Q(std::initializer_list<Q>) = delete; // #cwg2137-Qcons
   };
 
   Q x = Q { Q() };
   // since-cxx11-error at -1 {{call to deleted constructor of 'Q'}}
-  //   since-cxx11-note@#dr2137-Qcons {{'Q' has been explicitly marked deleted here}}
+  //   since-cxx11-note@#cwg2137-Qcons {{'Q' has been explicitly marked deleted here}}
 
-  int f(Q); // #dr2137-f
+  int f(Q); // #cwg2137-f
   int y = f({ Q() });
   // since-cxx11-error at -1 {{call to deleted constructor of 'Q'}}
-  //   since-cxx11-note@#dr2137-Qcons {{'Q' has been explicitly marked deleted here}}
-  //   since-cxx11-note@#dr2137-f {{passing argument to parameter here}}
+  //   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();
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index a065e3d54b748..c1ca97cf8b5d2 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -12630,7 +12630,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
     <td><a href="https://cplusplus.github.io/CWG/issues/2137.html">2137</a></td>
     <td>CD4</td>
     <td>List-initialization from object of same type</td>
-    <td class="unreleased" align="center">Clang 18</td>
+    <td class="unreleased" align="center">Clang 19</td>
   </tr>
   <tr id="2138">
     <td><a href="https://cplusplus.github.io/CWG/issues/2138.html">2138</a></td>

>From db3ee6a76212d46096105448aef80595ab8bbe7e Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Sun, 23 Jul 2023 18:17:18 +0100
Subject: [PATCH 3/5] [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

This is a cherry-pick of https://github.com/llvm/llvm-project/pull/77768/commits/a3b62a473a94a4b8f8eded611e3814810b8026ae
---
 clang/docs/ReleaseNotes.rst    |  4 ++
 clang/lib/Sema/SemaInit.cpp    | 34 +++++++++++---
 clang/test/CXX/drs/cwg23xx.cpp | 85 ++++++++++++++++++++++++++++++++++
 clang/www/cxx_dr_status.html   |  2 +-
 4 files changed, 117 insertions(+), 8 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 83ab694fc77cb..f9054a55a37d3 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -258,6 +258,10 @@ Resolutions to C++ Defect Reports
   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>`)
 
+- 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)``.
+
 C Language Changes
 ------------------
 
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 6aa0ebeeaa11f..c43e713ba8b92 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -4261,6 +4261,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
@@ -4271,13 +4279,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>();
@@ -4322,6 +4336,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:
diff --git a/clang/test/CXX/drs/cwg23xx.cpp b/clang/test/CXX/drs/cwg23xx.cpp
index e4a1e90941dbf..0064b0b90c437 100644
--- a/clang/test/CXX/drs/cwg23xx.cpp
+++ b/clang/test/CXX/drs/cwg23xx.cpp
@@ -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>
@@ -94,6 +104,81 @@ struct Z : W,
 // cwg2331: na
 // cwg2335 is in cwg2335.cxx
 
+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 at -1 {{call to deleted constructor of 'X<std::initializer_list<any>>'}}
+//   since-cxx17-note@#dr2311-X {{'X' has been explicitly marked deleted here}}
+#endif
+}
+
 #if __cplusplus >= 201103L
 namespace cwg2338 { // cwg2338: 12
 namespace B {
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index c1ca97cf8b5d2..28b6610941e58 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -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>

>From cdc8f5da360790572837bc3081dac4b3f3791d61 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Tue, 4 Jun 2024 12:54:00 +0100
Subject: [PATCH 4/5] [NFC] dr -> cwg, reword ReleaseNotes

---
 clang/docs/ReleaseNotes.rst    | 6 ++++--
 clang/test/CXX/drs/cwg23xx.cpp | 6 +++---
 clang/www/cxx_dr_status.html   | 2 +-
 3 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index f9054a55a37d3..285cc1a552616 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -258,9 +258,11 @@ Resolutions to C++ Defect Reports
   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>`)
 
-- 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).
+- 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
 ------------------
diff --git a/clang/test/CXX/drs/cwg23xx.cpp b/clang/test/CXX/drs/cwg23xx.cpp
index 0064b0b90c437..ecfe09dca95b1 100644
--- a/clang/test/CXX/drs/cwg23xx.cpp
+++ b/clang/test/CXX/drs/cwg23xx.cpp
@@ -104,7 +104,7 @@ struct Z : W,
 // cwg2331: na
 // cwg2335 is in cwg2335.cxx
 
-namespace dr2311 {  // dr2311: 18 open
+namespace cwg2311 {  // cwg2311 is open with no proposed resolution
 #if __cplusplus >= 201707L
 template<typename T>
 void test() {
@@ -170,12 +170,12 @@ struct any {
 template<typename T>
 struct X {
     X();
-    X(T) = delete; // #dr2311-X
+    X(T) = delete; // #cwg2311-X
 };
 
 X<std::initializer_list<any>> x{ X<std::initializer_list<any>>() };
 // since-cxx17-error at -1 {{call to deleted constructor of 'X<std::initializer_list<any>>'}}
-//   since-cxx17-note@#dr2311-X {{'X' has been explicitly marked deleted here}}
+//   since-cxx17-note@#cwg2311-X {{'X' has been explicitly marked deleted here}}
 #endif
 }
 
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 28b6610941e58..c1ca97cf8b5d2 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -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 class="unreleased" align="center">Clang 18</td>
+    <td align="center">Not resolved</td>
   </tr>
   <tr id="2312">
     <td><a href="https://cplusplus.github.io/CWG/issues/2312.html">2312</a></td>

>From d90534729d34da86bb01c4ddc272310f0f0b4ce4 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Tue, 4 Jun 2024 14:07:04 +0100
Subject: [PATCH 5/5] Fix CopyConstructor bug, add more tests

---
 clang/lib/Sema/SemaInit.cpp                   | 16 ++++
 clang/lib/Sema/SemaOverload.cpp               |  3 +-
 clang/test/CXX/drs/cwg23xx.cpp                | 14 ++++
 ...xx1z-class-template-argument-deduction.cpp |  5 +-
 .../test/SemaCXX/single-element-init-list.cpp | 81 +++++++++++++++++++
 5 files changed, 116 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/SemaCXX/single-element-init-list.cpp

diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index c43e713ba8b92..100e3092c1c1e 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -4285,6 +4285,22 @@ static void TryConstructorInitialization(Sema &S,
       // 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;
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index b6d6d9d1ef3af..3dd8719ad71d5 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1650,8 +1650,7 @@ TryUserDefinedConversion(Sema &S, Expr *From, QualType ToType,
           S.Context.getCanonicalType(FromType.getUnqualifiedType());
       QualType ToCanon
         = S.Context.getCanonicalType(ToType).getUnqualifiedType();
-      if (Constructor->isCopyConstructor() &&
-          (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.
diff --git a/clang/test/CXX/drs/cwg23xx.cpp b/clang/test/CXX/drs/cwg23xx.cpp
index ecfe09dca95b1..77fd6a337436e 100644
--- a/clang/test/CXX/drs/cwg23xx.cpp
+++ b/clang/test/CXX/drs/cwg23xx.cpp
@@ -176,6 +176,20 @@ struct X {
 X<std::initializer_list<any>> x{ X<std::initializer_list<any>>() };
 // since-cxx17-error at -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 at -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
 }
 
diff --git a/clang/test/SemaCXX/cxx1z-class-template-argument-deduction.cpp b/clang/test/SemaCXX/cxx1z-class-template-argument-deduction.cpp
index 90404f115c75f..977cb1d6e16e2 100644
--- a/clang/test/SemaCXX/cxx1z-class-template-argument-deduction.cpp
+++ b/clang/test/SemaCXX/cxx1z-class-template-argument-deduction.cpp
@@ -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 at -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>);
diff --git a/clang/test/SemaCXX/single-element-init-list.cpp b/clang/test/SemaCXX/single-element-init-list.cpp
new file mode 100644
index 0000000000000..bebd6f7ff3c42
--- /dev/null
+++ b/clang/test/SemaCXX/single-element-init-list.cpp
@@ -0,0 +1,81 @@
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s
+
+// This is heavily affected by the speculative resolution applied to CWG2311
+// So behaviour shown here is subject to change.
+
+// expected-no-diagnostics
+
+namespace std {
+  typedef decltype(sizeof(int)) size_t;
+
+  // libc++'s implementation
+  template <class _E>
+  class initializer_list
+  {
+    const _E* __begin_;
+    size_t    __size_;
+
+    initializer_list(const _E* __b, size_t __s)
+      : __begin_(__b),
+        __size_(__s)
+    {}
+
+  public:
+    typedef _E        value_type;
+    typedef const _E& reference;
+    typedef const _E& const_reference;
+    typedef size_t    size_type;
+
+    typedef const _E* iterator;
+    typedef const _E* const_iterator;
+
+    constexpr initializer_list() : __begin_(nullptr), __size_(0) {}
+
+    constexpr size_t    size()  const {return __size_;}
+    const _E* begin() const {return __begin_;}
+    const _E* end()   const {return __begin_ + __size_;}
+  };
+
+  template<typename T>
+  struct vector {
+    size_t sz;
+    constexpr vector() : sz(0) {}
+    constexpr vector(initializer_list<T> ilist) : sz(ilist.size()) {}
+    constexpr vector(const vector& other) : sz(other.sz) {}
+    constexpr std::size_t size() const { return sz; }
+  };
+}
+
+// https://github.com/llvm/llvm-project/pull/77768#issuecomment-1908062472
+namespace Issue1 {
+  struct A {
+    constexpr A() {}
+  };
+
+  struct B {
+    int called_ctor;
+    constexpr explicit B(A) : called_ctor(0) {}
+    constexpr explicit B(std::vector<A>) : called_ctor(1) {}
+  };
+
+  struct C {
+    B b;
+    constexpr C() : b({A()}) {}
+  };
+
+  static_assert(C().b.called_ctor == 0);
+}
+
+// https://github.com/llvm/llvm-project/pull/77768#issuecomment-1957171805
+namespace Issue2 {
+  struct A {
+    constexpr A(int x_) {}
+    constexpr A(const std::vector<A>& a) {}
+  };
+
+  void f() {
+    constexpr std::vector<A> a{1,2};
+    constexpr std::vector<A> b{a};  // This should call the initializer_list constructor
+    static_assert(b.size() == 1);
+  }
+}



More information about the cfe-commits mailing list