llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Nhat Nguyen  (changkhothuychung)

<details>
<summary>Changes</summary>

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.

---

Patch is 33.62 KiB, truncated to 20.00 KiB below, full version: 
https://github.com/llvm/llvm-project/pull/232978.diff


8 Files Affected:

- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+4) 
- (modified) clang/include/clang/Sema/Initialization.h (+3) 
- (modified) clang/include/clang/Sema/Overload.h (+6) 
- (modified) clang/include/clang/Sema/Sema.h (+11) 
- (modified) clang/lib/Sema/SemaInit.cpp (+147-62) 
- (modified) clang/lib/Sema/SemaOverload.cpp (+113-18) 
- (modified) clang/test/CXX/drs/cwg23xx.cpp (+111) 
- (added) clang/test/SemaCXX/cxx17-conversion-initialization.cpp (+110) 


``````````diff
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 paramete...
[truncated]

``````````

</details>


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

Reply via email to