https://github.com/changkhothuychung updated https://github.com/llvm/llvm-project/pull/232978
>From 3a473565c35c2b7e03bccabff0454f1d4190ec7d Mon Sep 17 00:00:00 2001 From: changkhothuychung <[email protected]> Date: Fri, 16 Oct 2026 19:55:41 -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 | 207 ++++++++++++------ clang/lib/Sema/SemaOverload.cpp | 135 ++++++++++-- clang/test/CXX/drs/cwg23xx.cpp | 111 ++++++++++ .../cxx17-conversion-initialization.cpp | 110 ++++++++++ 8 files changed, 507 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..105ffc2ccf4f3 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..e4dcb0c48314c 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,70 @@ 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 +4723,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 +4754,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 +7418,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 +7559,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 +9493,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 +9604,20 @@ 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 +9795,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..4b0d53fe4272e 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,15 @@ 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 +8892,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 +8909,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 +11325,54 @@ 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 +11404,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..55ede1c2b9944 --- /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::PrivateMember'}} + // 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
