https://github.com/changkhothuychung created 
https://github.com/llvm/llvm-project/pull/232978

This PR attempts to address 
[CWG2327](https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2327), 
the proposed changes can be found 
[here](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p2828r3.html), 
there is currently a wording issue edit in the section over.ics.list, instead 
of `selects a constructor C`,  we want it to be `a constructor or a conversion 
function F` instead, so it will match with the new rule of 
conversion-initialization.

>From 82c6487b43633e3e9aa648db54cc62865713143f Mon Sep 17 00:00:00 2001
From: changkhothuychung <[email protected]>
Date: Fri, 16 Oct 2026 19:05:38 -0400
Subject: [PATCH] CWG2327: Copy elision for direct-initialization with a
 conversion function

---
 .../clang/Basic/DiagnosticSemaKinds.td        |   4 +
 clang/include/clang/Sema/Initialization.h     |   3 +
 clang/include/clang/Sema/Overload.h           |   6 +
 clang/include/clang/Sema/Sema.h               |  11 +
 clang/lib/Sema/SemaInit.cpp                   | 209 ++++++++++++------
 clang/lib/Sema/SemaOverload.cpp               | 131 +++++++++--
 clang/test/CXX/drs/cwg23xx.cpp                | 111 ++++++++++
 .../cxx17-conversion-initialization.cpp       | 110 +++++++++
 8 files changed, 505 insertions(+), 80 deletions(-)
 create mode 100644 clang/test/SemaCXX/cxx17-conversion-initialization.cpp

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index cc87f556d50dc..7562f4180278d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2687,6 +2687,10 @@ def warn_cxx98_compat_temp_copy : Warning<
   InGroup<CXX98CompatBindToTemporaryCopy>, DefaultIgnore;
 def err_selected_explicit_constructor : Error<
   "chosen constructor is explicit in copy-initialization">;
+def err_selected_explicit_conversion_function : Error<
+  "chosen conversion function is explicit in copy-list-initialization">;
+def note_explicit_conversion_function_here : Note<
+  "explicit conversion function declared here">;
 def note_explicit_ctor_deduction_guide_here : Note<
   "explicit %select{constructor|deduction guide}0 declared here">;
 def note_implicit_deduction_guide : Note<"implicit deduction guide declared as 
'%0'">;
diff --git a/clang/include/clang/Sema/Initialization.h 
b/clang/include/clang/Sema/Initialization.h
index 2c2df1f03747b..c0dd475fd982a 100644
--- a/clang/include/clang/Sema/Initialization.h
+++ b/clang/include/clang/Sema/Initialization.h
@@ -1158,6 +1158,9 @@ class InitializationSequence {
     /// List-copy-initialization chose an explicit constructor.
     FK_ExplicitConstructor,
 
+    /// Copy-list-initialization chose an explicit conversion function.
+    FK_ExplicitConversionFunction,
+
     /// Parenthesized list initialization failed at some point.
     /// This is a C++20 feature.
     FK_ParenthesizedListInitFailed,
diff --git a/clang/include/clang/Sema/Overload.h 
b/clang/include/clang/Sema/Overload.h
index f280c6ffa5a81..6ce13a4ad0e55 100644
--- a/clang/include/clang/Sema/Overload.h
+++ b/clang/include/clang/Sema/Overload.h
@@ -1184,6 +1184,11 @@ class Sema;
       /// using either a parenthesized or braced list of arguments.
       CSK_InitByConstructor,
 
+      /// C++ [over.match.ctor], [over.match.list]
+      /// Initialization of an object of class type by constructor
+      /// in the context of direct-initialization
+      CSK_DirectInitByConstructor,
+
       /// C++ [over.match.call.general]
       /// Resolve a call through the address of an overload set.
       CSK_AddressOfOverloadSet,
@@ -1493,6 +1498,7 @@ class Sema;
 
     void setDestAS(LangAS AS) {
       assert((Kind == CSK_InitByConstructor ||
+              Kind == CSK_DirectInitByConstructor ||
               Kind == CSK_InitByUserDefinedConversion) &&
              "can't set the destination address space when not constructing an 
"
              "object");
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 4c51a3c03c80c..a386205ef6306 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -10687,6 +10687,17 @@ class Sema final : public SemaBase {
       OverloadCandidateSet &CandidateSet, bool AllowObjCConversionOnExplicit,
       bool AllowExplicit, bool AllowResultConversion = true);
 
+  /// Adds a conversion functions as candidate to the overload set,
+  /// in the case that  no candidate function is viable and
+  /// the initializer is a parenthesized expression-list containing
+  /// a single element or a braced-init-list containing a single element,
+  /// and the cv-unqualified type U of that element is neither the same class 
as, 
+  /// nor a derived class of, the cv-unqualified type T of the object being 
initialized
+  /// [over.match.ctor]
+  void AddConversionFunctionCandidate(Expr *Initializer, QualType DestType,
+                                         OverloadCandidateSet &CandidateSet,
+                                         bool AllowExplicit);
+
   /// AddSurrogateCandidate - Adds a "surrogate" candidate function that
   /// converts the given @c Object to a function pointer via the
   /// conversion function @c Conversion, and then attempts to call it
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index d2aa8691102aa..c835dce48a431 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -4043,6 +4043,7 @@ bool InitializationSequence::isAmbiguous() const {
   case FK_VariableLengthArrayHasInitializer:
   case FK_PlaceholderType:
   case FK_ExplicitConstructor:
+  case FK_ExplicitConversionFunction:
   case FK_AddressOfUnaddressableFunction:
   case FK_ParenthesizedListInitFailed:
   case FK_DesignatedInitForNonAggregate:
@@ -4477,9 +4478,11 @@ static OverloadingResult ResolveConstructorOverload(
     OverloadCandidateSet &CandidateSet, QualType DestType,
     DeclContext::lookup_result Ctors, OverloadCandidateSet::iterator &Best,
     bool CopyInitializing, bool AllowExplicit, bool OnlyListConstructors,
-    bool IsListInit, bool RequireActualConstructor,
-    bool SecondStepOfCopyInit = false) {
-  CandidateSet.clear(OverloadCandidateSet::CSK_InitByConstructor);
+    bool IsListInit, bool SecondStepOfCopyInit = false) {
+  const bool IsDirectInitialization = !CopyInitializing; 
+  CandidateSet.clear(IsDirectInitialization
+                         ? OverloadCandidateSet::CSK_DirectInitByConstructor
+                         : OverloadCandidateSet::CSK_InitByConstructor);
   CandidateSet.setDestAS(DestType.getQualifiers().getAddressSpace());
 
   for (NamedDecl *D : Ctors) {
@@ -4529,51 +4532,71 @@ static OverloadingResult ResolveConstructorOverload(
     }
   }
 
-  // FIXME: Work around a bug in C++17 guaranteed copy elision.
-  //
-  // When initializing an object of class type T by constructor
-  // ([over.match.ctor]) or by list-initialization ([over.match.list])
-  // from a single expression of class type U, conversion functions of
-  // U that convert to the non-reference type cv T are candidates.
-  // Explicit conversion functions are only candidates during
-  // direct-initialization.
-  //
-  // Note: SecondStepOfCopyInit is only ever true in this case when
-  // evaluating whether to produce a C++98 compatibility warning.
-  if (S.getLangOpts().CPlusPlus17 && Args.size() == 1 &&
-      !RequireActualConstructor && !SecondStepOfCopyInit) {
-    Expr *Initializer = Args[0];
-    auto *SourceRD = Initializer->getType()->getAsCXXRecordDecl();
-    if (SourceRD && S.isCompleteType(DeclLoc, Initializer->getType())) {
-      const auto &Conversions = SourceRD->getVisibleConversionFunctions();
-      for (auto I = Conversions.begin(), E = Conversions.end(); I != E; ++I) {
-        NamedDecl *D = *I;
-        CXXRecordDecl *ActingDC = cast<CXXRecordDecl>(D->getDeclContext());
-        D = D->getUnderlyingDecl();
+  // Perform overload resolution and return the result.
+  return CandidateSet.BestViableFunction(S, DeclLoc, Best);
+}
 
-        FunctionTemplateDecl *ConvTemplate = dyn_cast<FunctionTemplateDecl>(D);
-        CXXConversionDecl *Conv;
-        if (ConvTemplate)
-          Conv = cast<CXXConversionDecl>(ConvTemplate->getTemplatedDecl());
-        else
-          Conv = cast<CXXConversionDecl>(D);
+/// Add the steps that initialize an object of class type DestType from the
+/// prvalue result of calling the conversion function Conv.
+static void AddConversionFunctionSteps(Sema &S, const InitializedEntity 
&Entity,
+                                       QualType DestType,
+                                       CXXConversionDecl *Conv,
+                                       DeclAccessPair FoundDecl,
+                                       bool HadMultipleCandidates,
+                                       InitListExpr *ILE,
+                                       InitializationSequence &Sequence) {
+  QualType ConvType = Conv->getConversionType();
+  Sequence.AddUserConversionStep(Conv, FoundDecl, ConvType,
+                                 HadMultipleCandidates);
+  if (!S.Context.hasSameType(ConvType, DestType))
+    Sequence.AddQualificationConversionStep(DestType, VK_PRValue);
+  if (ILE)
+    Sequence.RewrapReferenceInitList(Entity.getType(), ILE);
+}
+
+
+/// Attempts conversion-initialization (C++ [dcl.init]). If the context
+/// is direct-initialization, and the selected constructor is a copy or 
+/// move constructor ([class.copy.ctor]) for the class of the destination, 
+/// the initializer is a parenthesized expression-list containing a single 
+/// element, and the implicit conversion sequence ([over.best.ics.general]) 
+/// for the constructor's first parameter would bind that parameter to the
+/// result object of a conversion function whose return type, ignoring
+/// cv-qualification, is the class of the destination, the prvalue result of 
+/// calling the conversion function is used to initialize the destination 
object.
+/// This initialization is called conversion-initialization.
+static bool TryConversionInitialization(
+    Sema &S, const InitializedEntity &Entity, const InitializationKind &Kind,
+    QualType DestType, OverloadCandidateSet &CandidateSet,
+    OverloadCandidateSet::iterator Best, OverloadingResult Result,
+    bool IsListInit, InitListExpr *ILE, InitializationSequence &Sequence) {
+  if (Result != OR_Success && Result != OR_Deleted)
+    return false;
 
-        if (ConvTemplate)
-          S.AddTemplateConversionCandidate(
-              ConvTemplate, I.getPair(), ActingDC, Initializer, DestType,
-              CandidateSet, AllowExplicit, AllowExplicit,
-              /*AllowResultConversion*/ false);
-        else
-          S.AddConversionCandidate(Conv, I.getPair(), ActingDC, Initializer,
-                                   DestType, CandidateSet, AllowExplicit,
-                                   AllowExplicit,
-                                   /*AllowResultConversion*/ false);
-      }
-    }
+  auto *Ctor = dyn_cast<CXXConstructorDecl>(Best->Function);
+  if (!Ctor || !Ctor->isCopyOrMoveConstructor() ||
+      !Best->Conversions[0].isUserDefined())
+    return false;
+
+  const UserDefinedConversionSequence &User = Best->Conversions[0].UserDefined;
+  auto *Conv = dyn_cast_or_null<CXXConversionDecl>(User.ConversionFunction);
+  if (!Conv || Conv->getConversionType()->isReferenceType() ||
+      !S.Context.hasSameUnqualifiedType(Conv->getConversionType(), DestType))
+    return false;
+
+  // C++11 [over.match.list]p1:
+  //   In copy-list-initialization, if an explicit constructor is chosen, the
+  //   initializer is ill-formed.
+  if (IsListInit && !Kind.AllowExplicit() && Ctor->isExplicit()) {
+    Sequence.SetFailed(InitializationSequence::FK_ExplicitConstructor);
+    return true;
   }
 
-  // Perform overload resolution and return the result.
-  return CandidateSet.BestViableFunction(S, DeclLoc, Best);
+  AddConversionFunctionSteps(S, Entity, DestType, Conv,
+                             User.FoundConversionFunction,
+                             /*HadMultipleCandidates=*/CandidateSet.size() > 1,
+                             IsListInit ? ILE : nullptr, Sequence);
+  return true;
 }
 
 /// Attempt initialization by constructor (C++ [dcl.init]), which
@@ -4701,7 +4724,7 @@ static void TryConstructorInitialization(Sema &S,
       Result = ResolveConstructorOverload(
           S, Kind.getLocation(), Args, CandidateSet, DestType, Ctors, Best,
           CopyInitialization, AllowExplicit,
-          /*OnlyListConstructors=*/true, IsListInit, RequireActualConstructor);
+          /*OnlyListConstructors=*/true, IsListInit);
 
     if (CopyElisionPossible && Result == OR_No_Viable_Function) {
       // No initializer list candidate
@@ -4732,33 +4755,66 @@ static void TryConstructorInitialization(Sema &S,
     Result = ResolveConstructorOverload(
         S, Kind.getLocation(), UnwrappedArgs, CandidateSet, DestType, Ctors,
         Best, CopyInitialization, AllowExplicit,
-        /*OnlyListConstructors=*/false, IsListInit, RequireActualConstructor);
+        /*OnlyListConstructors=*/false, IsListInit);
+  }
+
+  /// If the initializer is a parenthesized expression-list,
+  /// or braced-init-list containing a single element whose type is neither
+  /// the same class as, nor a derived class of, the destination type, attempt
+  /// to perform conversion-initialization
+  bool ShouldTryConversionInit =
+      S.getLangOpts().CPlusPlus17 && !RequireActualConstructor &&
+      UnwrappedArgs.size() == 1 &&
+      (Kind.getKind() == InitializationKind::IK_Direct || IsListInit) &&
+      !S.Context.hasSameUnqualifiedType(UnwrappedArgs[0]->getType(),
+                                        DestType) &&
+      !S.IsDerivedFrom(Kind.getLocation(), UnwrappedArgs[0]->getType(),
+                       DestType);
+
+  if (ShouldTryConversionInit &&
+      TryConversionInitialization(S, Entity, Kind, DestType, CandidateSet, 
Best,
+                                  Result, IsListInit, ILE, Sequence))
+    return;
+
+  // [over.match.ctor]: If no candidate function is viable 
+  // and the initializer is a parenthesized expression-list 
+  // containing a single element or a braced-init-list containing 
+  // a single element, and the cv-unqualified type U of that element 
+  // is neither the same class as, nor a derived class of, 
+  // the cv-unqualified type T of the object being initialized, 
+  // overload resolution is then performed again, 
+  // where the candidate functions are enumerated 
+  // by looking up operator cv T in U for each possible cv
+  if (ShouldTryConversionInit && Result == OR_No_Viable_Function) {
+    S.AddConversionFunctionCandidate(UnwrappedArgs[0], DestType,
+                                        CandidateSet, /*AllowExplicit=*/true);
+    Result = CandidateSet.BestViableFunction(S, Kind.getLocation(), Best);
   }
+
   if (Result) {
     Sequence.SetOverloadFailure(
         IsListInit ? InitializationSequence::FK_ListConstructorOverloadFailed
                    : InitializationSequence::FK_ConstructorOverloadFailed,
         Result);
 
-    if (Result != OR_Deleted)
+    if (Result != OR_Deleted || !isa<CXXConstructorDecl>(Best->Function))
       return;
   }
 
   bool HadMultipleCandidates = (CandidateSet.size() > 1);
 
-  // In C++17, ResolveConstructorOverload can select a conversion function
-  // instead of a constructor.
-  if (auto *CD = dyn_cast<CXXConversionDecl>(Best->Function)) {
-    // Add the user-defined conversion step that calls the conversion function.
-    QualType ConvType = CD->getConversionType();
-    assert(S.Context.hasSameUnqualifiedType(ConvType, DestType) &&
-           "should not have selected this conversion function");
-    Sequence.AddUserConversionStep(CD, Best->FoundDecl, ConvType,
-                                   HadMultipleCandidates);
-    if (!S.Context.hasSameType(ConvType, DestType))
-      Sequence.AddQualificationConversionStep(DestType, VK_PRValue);
-    if (IsListInit)
-      Sequence.RewrapReferenceInitList(Entity.getType(), ILE);
+  // The overload resolution found a conversion function as a candidate. It is
+  // used to initialize the object.
+  if (auto *Conv = dyn_cast<CXXConversionDecl>(Best->Function)) {
+    // [over.match.list]: if an explicit conversion function
+    // is chosen, the initialization is ill-formed.
+    if (IsListInit && !Kind.AllowExplicit() && Conv->isExplicit()) {
+      
Sequence.SetFailed(InitializationSequence::FK_ExplicitConversionFunction);
+      return;
+    }
+    AddConversionFunctionSteps(S, Entity, DestType, Conv, Best->FoundDecl,
+                               HadMultipleCandidates,
+                               IsListInit ? ILE : nullptr, Sequence);
     return;
   }
 
@@ -7363,7 +7419,6 @@ static ExprResult CopyObject(Sema &S,
       S, Loc, CurInitExpr, CandidateSet, T, Ctors, Best,
       /*CopyInitializing=*/false, /*AllowExplicit=*/true,
       /*OnlyListConstructors=*/false, /*IsListInit=*/false,
-      /*RequireActualConstructor=*/false,
       /*SecondStepOfCopyInit=*/true)) {
   case OR_Success:
     break;
@@ -7505,7 +7560,6 @@ static void CheckCXX98CompatAccessibleCopy(Sema &S,
       S, Loc, CurInitExpr, CandidateSet, CurInitExpr->getType(), Ctors, Best,
       /*CopyInitializing=*/false, /*AllowExplicit=*/true,
       /*OnlyListConstructors=*/false, /*IsListInit=*/false,
-      /*RequireActualConstructor=*/false,
       /*SecondStepOfCopyInit=*/true);
 
   PartialDiagnostic Diag = S.PDiag(diag::warn_cxx98_compat_temp_copy)
@@ -9440,6 +9494,18 @@ bool InitializationSequence::Diagnose(Sema &S,
           break;
         }
 
+        // We will reach this case if we are in the context of
+        // conversion initialization and the second phase of
+        // overload resolution selects a deleted conversion function.
+        if (isa<CXXConversionDecl>(Best->Function)) {
+          StringLiteral *Msg = Best->Function->getDeletedMessage();
+          S.Diag(Kind.getLocation(), diag::err_typecheck_deleted_function)
+              << Args[0]->getType() << DestType << (Msg != nullptr)
+              << (Msg ? Msg->getString() : StringRef()) << ArgsRange;
+          S.NoteDeletedFunction(Best->Function);
+          break;
+        }
+
         // If this is a defaulted or implicitly-declared function, then
         // it was implicitly deleted. Make it clear that the deletion was
         // implicit.
@@ -9539,6 +9605,21 @@ bool InitializationSequence::Diagnose(Sema &S,
     break;
   }
 
+  case FK_ExplicitConversionFunction: {
+    S.Diag(Kind.getLocation(),
+           diag::err_selected_explicit_conversion_function)
+        << Args[0]->getSourceRange();
+    OverloadCandidateSet::iterator Best;
+    OverloadingResult Ovl =
+        FailedCandidateSet.BestViableFunction(S, Kind.getLocation(), Best);
+    (void)Ovl;
+    assert(Ovl == OR_Success && "Inconsistent overload resolution");
+    auto *ConvDecl = cast<CXXConversionDecl>(Best->Function);
+    S.Diag(ConvDecl->getLocation(),
+           diag::note_explicit_conversion_function_here);
+    break;
+  }
+
   case FK_ParenthesizedListInitFailed:
     TryOrBuildParenListInitialization(S, Entity, Kind, Args, *this,
                                       /*VerifyOnly=*/false);
@@ -9716,6 +9797,10 @@ void InitializationSequence::dump(raw_ostream &OS) const 
{
       OS << "list copy initialization chose explicit constructor";
       break;
 
+    case FK_ExplicitConversionFunction:
+      OS << "list copy initialization chose explicit conversion function";
+      break;
+
     case FK_ParenthesizedListInitFailed:
       OS << "parenthesized list initialization failed";
       break;
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 62b6ad8ffb564..66b166ae94ad7 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -4342,11 +4342,26 @@ IsUserDefinedConversion(Sema &S, Expr *From, QualType 
ToType,
     }
   }
 
+  OverloadCandidateSet::iterator Best;
+  OverloadingResult Result =
+      CandidateSet.BestViableFunction(S, From->getBeginLoc(), Best);
+
+  // [over.ics.list]: If we found no constructor 
+  // and the initializer is a braced-init-list
+  // containing a single element, we will consider
+  // conversion functions as candidates.
+  if (auto *InitList = dyn_cast<InitListExpr>(From);
+      InitList && Result == OR_No_Viable_Function &&
+      S.getLangOpts().CPlusPlus17 && InitList->getNumInits() == 1 &&
+      ToType->isRecordType()) {
+    S.AddConversionFunctionCandidate(InitList->getInit(0), ToType, 
CandidateSet,
+                                     AllowExplicit != AllowedExplicit::None);
+    Result = CandidateSet.BestViableFunction(S, From->getBeginLoc(), Best);
+  }
+
   bool HadMultipleCandidates = (CandidateSet.size() > 1);
 
-  OverloadCandidateSet::iterator Best;
-  switch (auto Result =
-              CandidateSet.BestViableFunction(S, From->getBeginLoc(), Best)) {
+  switch (Result) {
   case OR_Success:
   case OR_Deleted:
     // Record the standard conversion we used and the conversion function.
@@ -5495,7 +5510,6 @@ FindConversionForRefInit(Sema &S, 
ImplicitConversionSequence &ICS,
   OverloadCandidateSet::iterator Best;
   switch (CandidateSet.BestViableFunction(S, DeclLoc, Best)) {
   case OR_Success:
-
     assert(Best->HasFinalConversion);
 
     // C++ [over.ics.ref]p1:
@@ -5767,8 +5781,14 @@ TryReferenceInit(Sema &S, Expr *Init, QualType DeclType,
   //   the argument expression. Any difference in top-level
   //   cv-qualification is subsumed by the initialization itself
   //   and does not constitute a conversion.
+  // C++ [over.match.copy]p1.2: When initializing a temporary object to be 
bound 
+  // to the first parameter of a constructor where the parameter is of type 
+  // “reference to cv2 T” and the constructor is called with a single argument 
in 
+  // the context of direct-initialization of an object of type “cv3 T”, the 
permissible 
+  // types for explicit conversion functions are the same; otherwise there are 
none.
   ICS = TryImplicitConversion(S, Init, T1, SuppressUserConversions,
-                              AllowedExplicit::None,
+                              AllowExplicit ? AllowedExplicit::Conversions
+                                            : AllowedExplicit::None,
                               /*InOverloadResolution=*/false,
                               /*CStyle=*/false,
                               /*AllowObjCWritebackConversion=*/false,
@@ -8871,7 +8891,9 @@ void Sema::AddTemplateConversionCandidate(
   if (!CandidateSet.shouldDeferTemplateArgumentDeduction(*this) ||
       CandidateSet.getKind() ==
           OverloadCandidateSet::CSK_InitByUserDefinedConversion ||
-      CandidateSet.getKind() == OverloadCandidateSet::CSK_InitByConstructor) {
+      CandidateSet.getKind() == OverloadCandidateSet::CSK_InitByConstructor ||
+      CandidateSet.getKind() ==
+          OverloadCandidateSet::CSK_DirectInitByConstructor) {
     AddTemplateConversionCandidateImmediately(
         *this, CandidateSet, FunctionTemplate, FoundDecl, ActingDC, From,
         ToType, AllowObjCConversionOnExplicit, AllowExplicit,
@@ -8886,6 +8908,46 @@ void Sema::AddTemplateConversionCandidate(
       AllowObjCConversionOnExplicit, AllowExplicit, AllowResultConversion);
 }
 
+void Sema::AddConversionFunctionCandidate(
+    Expr *Initializer, QualType DestType, OverloadCandidateSet &CandidateSet,
+    bool AllowExplicit) {
+  SourceLocation DeclLoc = Initializer->getBeginLoc();
+
+  // If the initializer has eiuther the same type or derived class
+  // type with the object being initialized, then do nothing.
+  if (Context.hasSameUnqualifiedType(Initializer->getType(), DestType) ||
+      IsDerivedFrom(DeclLoc, Initializer->getType(), DestType))
+    return;
+
+  auto *SourceRD = Initializer->getType()->getAsCXXRecordDecl();
+  if (SourceRD && isCompleteType(DeclLoc, Initializer->getType())) {
+    const auto &Conversions = SourceRD->getVisibleConversionFunctions();
+    for (auto I = Conversions.begin(), E = Conversions.end(); I != E; ++I) {
+      NamedDecl *D = *I;
+      CXXRecordDecl *ActingDC = cast<CXXRecordDecl>(D->getDeclContext());
+      D = D->getUnderlyingDecl();
+
+      FunctionTemplateDecl *ConvTemplate = dyn_cast<FunctionTemplateDecl>(D);
+      CXXConversionDecl *Conv;
+      if (ConvTemplate)
+        Conv = cast<CXXConversionDecl>(ConvTemplate->getTemplatedDecl());
+      else
+        Conv = cast<CXXConversionDecl>(D);
+
+      if (ConvTemplate)
+        AddTemplateConversionCandidate(
+            ConvTemplate, I.getPair(), ActingDC, Initializer, DestType,
+            CandidateSet, AllowExplicit, AllowExplicit,
+            /*AllowResultConversion*/ false);
+      else
+        AddConversionCandidate(Conv, I.getPair(), ActingDC, Initializer,
+                               DestType, CandidateSet, AllowExplicit,
+                               AllowExplicit,
+                               /*AllowResultConversion*/ false);
+    }
+  }
+}
+
 void Sema::AddSurrogateCandidate(CXXConversionDecl *Conversion,
                                  DeclAccessPair FoundDecl,
                                  CXXRecordDecl *ActingContext,
@@ -11262,6 +11324,51 @@ bool clang::isBetterOverloadCandidate(
   if (HasBetterConversion && !HasWorseConversion)
     return true;
 
+  // [over.match.best.general]:
+  // - the context is the direct-initialization of an object of type cv1 T, 
+  //   F1 is a copy or move constructor of T, F2 is not, the argument list 
+  //   has a single element, and ICS1(F1) is a user-defined conversion 
sequence U, such that:
+  //   - U's user-defined conversion is a conversion function whose return 
type is cv2 T, or
+  //   - U is the ambiguous conversion sequence, and one of the possible 
implicit conversion sequences 
+  //     for F1's parameter is such a user-defined conversion
+  if (Kind == OverloadCandidateSet::CSK_DirectInitByConstructor &&
+      S.getLangOpts().CPlusPlus17 && NumArgs == 1) {
+    auto *Ctor1 = dyn_cast_or_null<CXXConstructorDecl>(Cand1.Function);
+    auto *Ctor2 = dyn_cast_or_null<CXXConstructorDecl>(Cand2.Function);
+    if (Ctor1 && Ctor2) {
+
+      // Determines whether ICS is a user-defined conversion sequence
+      // in which there exists a conversion function as a step.
+      auto ICSHasConversionFunction = [&](const ImplicitConversionSequence 
&ICS,
+                                          const CXXRecordDecl *RD) {
+        QualType ClassType = S.Context.getCanonicalTagType(RD);
+        auto IsConversionToClassType = [&](const FunctionDecl *FD) {
+          auto *Conv = dyn_cast_or_null<CXXConversionDecl>(FD);
+          return Conv && !Conv->getConversionType()->isReferenceType() &&
+                 S.Context.hasSameUnqualifiedType(Conv->getConversionType(),
+                                                  ClassType);
+        };
+        if (ICS.isUserDefined())
+          return IsConversionToClassType(ICS.UserDefined.ConversionFunction);
+        if (ICS.isAmbiguous())
+          return llvm::any_of(ICS.Ambiguous.conversions(),
+                              [&](const auto &Conv) {
+                                return IsConversionToClassType(Conv.second);
+                              });
+        return false;
+      };
+
+      bool Ctor1IsCopyOrMove = Ctor1->isCopyOrMoveConstructor();
+      bool Ctor2IsCopyOrMove = Ctor2->isCopyOrMoveConstructor();
+      if (Ctor1IsCopyOrMove && !Ctor2IsCopyOrMove &&
+          ICSHasConversionFunction(Cand1.Conversions[0], Ctor1->getParent()))
+        return true;
+      if (Ctor2IsCopyOrMove && !Ctor1IsCopyOrMove &&
+          ICSHasConversionFunction(Cand2.Conversions[0], Ctor2->getParent()))
+        return false;
+    }
+  }
+
   //   -- the context is an initialization by user-defined conversion
   //      (see 8.5, 13.3.1.5) and the standard conversion sequence
   //      from the return type of F1 to the destination type (i.e.,
@@ -11293,18 +11400,6 @@ bool clang::isBetterOverloadCandidate(
     // C++14 [over.match.best]p1 section 2 bullet 3.
   }
 
-  // FIXME: Work around a defect in the C++17 guaranteed copy elision wording,
-  // as combined with the resolution to CWG issue 243.
-  //
-  // When the context is initialization by constructor ([over.match.ctor] or
-  // either phase of [over.match.list]), a constructor is preferred over
-  // a conversion function.
-  if (Kind == OverloadCandidateSet::CSK_InitByConstructor && NumArgs == 1 &&
-      Cand1.Function && Cand2.Function &&
-      isa<CXXConstructorDecl>(Cand1.Function) !=
-          isa<CXXConstructorDecl>(Cand2.Function))
-    return isa<CXXConstructorDecl>(Cand1.Function);
-
   if (Cand1.StrictPackMatch != Cand2.StrictPackMatch)
     return Cand2.StrictPackMatch;
 
diff --git a/clang/test/CXX/drs/cwg23xx.cpp b/clang/test/CXX/drs/cwg23xx.cpp
index cfd78465c0dbe..08c447b482b9b 100644
--- a/clang/test/CXX/drs/cwg23xx.cpp
+++ b/clang/test/CXX/drs/cwg23xx.cpp
@@ -193,6 +193,117 @@ auto j = std::initializer_list<InitListCtor>{ i };
 #endif
 } // namespace cwg2311
 
+namespace cwg2327 { // cwg2327: 23 drafting P2828R3
+
+#if __cplusplus >= 201703L
+namespace example1 {
+struct Cat {
+  Cat(int);
+  Cat(const Cat&) = delete;
+  Cat(Cat&&) = delete;
+};
+
+struct Dog { operator Cat(); };
+
+Dog d;
+Cat c(d);
+Cat c1 = {d};
+Cat c2{d};
+} // namespace example1
+
+namespace example2 {
+struct X {
+  X(int);
+  X(X&&);
+};
+
+struct Y {
+  operator X();
+  operator int();
+};
+
+X x(Y{});
+} // namespace example2
+
+namespace example4 {
+struct Dog;
+struct Cat {
+  Cat(const Dog&);
+};
+
+struct Dog {
+  operator Cat() = delete;
+};
+
+Cat cat(Dog{});
+} // namespace example4
+
+namespace example5 {
+struct A1 {};
+
+struct A2 {
+  A2(const A1&);
+  A2(const A2&);
+};
+
+struct B : A1 {
+  operator A2() = delete;
+};
+
+A2 a(B{});
+} // namespace example5
+
+namespace example6 {
+struct T {
+  T(T const&);
+};
+
+struct S {
+  operator T() = delete;
+  operator T&();
+};
+
+S s;
+T t(s);
+} // namespace example6
+
+namespace example7 {
+struct Y;
+
+struct X {
+  X(const Y&);
+};
+
+struct A { operator X(); };
+struct B { operator X(); };
+struct Y : A, B {};
+
+X x(Y{});
+} // namespace example7
+#endif // __cplusplus >= 201703L
+
+#if __cplusplus >= 202002L
+namespace example8 {
+template <int i = 0>
+class NonCopyable {
+public:
+  NonCopyable(const NonCopyable&) requires(i != 0);
+
+private:
+  NonCopyable(int x);
+  friend struct Source;
+};
+
+struct Source {
+  operator NonCopyable<0>();
+};
+
+NonCopyable<0> nc(Source{});
+} // namespace example8
+#endif // __cplusplus >= 202002L
+
+} // namespace cwg2327
+
 namespace cwg2338 { // cwg2338: 12
 #if __cplusplus >= 201103L
 namespace B {
diff --git a/clang/test/SemaCXX/cxx17-conversion-initialization.cpp 
b/clang/test/SemaCXX/cxx17-conversion-initialization.cpp
new file mode 100644
index 0000000000000..b6effb3097270
--- /dev/null
+++ b/clang/test/SemaCXX/cxx17-conversion-initialization.cpp
@@ -0,0 +1,110 @@
+// RUN: %clang_cc1 -std=c++17 -verify %s
+// RUN: %clang_cc1 -std=c++20 -verify %s
+// RUN: %clang_cc1 -std=c++23 -verify %s
+// RUN: %clang_cc1 -std=c++2c -verify %s
+
+namespace private_constructors {
+struct A {
+private:
+  A(const A &) = delete;
+  A(A &&) = delete;
+};
+struct S {
+  operator const A();
+};
+S s;
+A a(s);
+} // namespace private_constructors
+
+namespace no_conversion_init {
+struct A {
+  A(const A &) = delete;
+  A(A &&) = delete;
+};
+struct B : A {};
+struct C { operator A &(); };
+struct D { operator B(); };
+A a1(C{}); // expected-error {{call to deleted constructor of 'A'}}
+           // expected-note@-7 {{marked deleted here}}
+A a2(D{}); // expected-error {{call to deleted constructor of 'A'}}
+           // expected-note@-8 {{marked deleted here}}
+} // namespace no_conversion_init
+
+namespace explicit_functions {
+struct T { T(int); };
+struct S { explicit operator T(); };
+S s;
+T t1(s), t2{s};
+T t3 = {s}; // expected-error {{chosen conversion function is explicit in 
copy-list-initialization}}
+            // expected-note@-4 {{explicit conversion function declared here}}
+
+struct U { explicit U(const U &); };
+struct V { operator U(); };
+V v;
+U u1(v), u2{v};
+U u3 = {v}; // expected-error {{chosen constructor is explicit in 
copy-initialization}}
+            // expected-note@-5 {{explicit constructor declared here}}
+} // namespace explicit_functions
+
+namespace second_phase {
+struct A { A(A &); };
+struct X { operator A(); };
+A a(X{});
+
+#if __cplusplus >= 202002L
+template <int N = 0> struct B {
+  B(const B &) requires(N != 0);
+};
+struct S { operator B<>(); };
+struct Tmpl { template <class T> operator T(); };
+void f(B<>);
+B<> b1(S{});
+B<> b2(Tmpl{});
+void g(S s) { f({s}); }
+
+struct Ref { template <class T> operator T &(); };
+B<> b3(Ref{}); // expected-error {{no matching constructor for initialization 
of 'B<>'}}
+               // expected-note@-11 {{constraints not satisfied}}
+               // expected-note@-12 {{because '0 != 0' (0 != 0) evaluated to 
false}}
+
+struct Derived : B<> { operator B<>(); }; // expected-warning {{will never be 
used}}
+void h(Derived d) {
+  B<> b(d); // expected-error {{no matching constructor for initialization of 
'B<>'}}
+            // expected-note@-17 {{constraints not satisfied}}
+            // expected-note@-18 {{because '0 != 0' (0 != 0) evaluated to 
false}}
+}
+
+struct S2 { operator B<>(); };
+struct Ambiguous : S, S2 {};
+B<> b4(Ambiguous{}); // expected-error {{call to constructor of 'B<>' is 
ambiguous}}
+               // expected-note@-22 {{candidate function}}
+               // expected-note@-4 {{candidate function}}
+B<> b5 = {Ambiguous{}}; // expected-error {{call to constructor of 'B<>' is 
ambiguous}}
+                  // expected-note@-25 {{candidate function}}
+                  // expected-note@-7 {{candidate function}}
+
+struct DeletedConversion { operator B<>() = delete; };
+B<> b6(DeletedConversion{}); // expected-error {{conversion function from 
'DeletedConversion' to 'B<>' invokes a deleted function}}
+               // expected-note@-2 {{explicitly marked deleted here}}
+B<> b7 = {DeletedConversion{}}; // expected-error {{conversion function from 
'DeletedConversion' to 'B<>' invokes a deleted function}}
+                  // expected-note@-4 {{explicitly marked deleted here}}
+
+class PrivateMember { operator B<>(); };
+B<> b8(PrivateMember{}); // expected-error {{'operator B' is a private member 
of 'second_phase::Priv'}}
+                // expected-note@-2 {{implicitly declared private here}}
+
+struct NoConv {};
+B<> b9(NoConv{}); // expected-error {{no matching constructor for 
initialization of 'B<>'}}
+                  // expected-note@-42 {{constraints not satisfied}}
+                  // expected-note@-43 {{because '0 != 0' (0 != 0) evaluated 
to false}}
+B<> b10 = {NoConv{}}; // expected-error {{no matching constructor for 
initialization of 'B<>'}}
+                      // expected-note@-45 {{constraints not satisfied}}
+                      // expected-note@-46 {{because '0 != 0' (0 != 0) 
evaluated to false}}
+void noconv_func(NoConv n) { f({n}); } // expected-error {{no matching 
function for call to 'f'}}
+                              // expected-note@-44 {{cannot convert 
initializer list argument to 'B<>'}}
+
+struct ExplicitConv { explicit operator B<>(); };
+void explicitconv_func(ExplicitConv e) { f({e}); } // expected-error {{no 
matching function for call to 'f'}}
+                                    // expected-note@-48 {{cannot convert 
initializer list argument to 'B<>'}}
+#endif
+} // namespace second_phase

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to