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
