[clang] CWG2327: Copy elision for direct-initialization with a conversion fun… (PR #232978)
Nhat Nguyen via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 16 16:09:26 PDT 2026
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 <nhat7203 at gmail.com>
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 at -7 {{marked deleted here}}
+A a2(D{}); // expected-error {{call to deleted constructor of 'A'}}
+ // expected-note at -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 at -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 at -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 at -11 {{constraints not satisfied}}
+ // expected-note at -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 at -17 {{constraints not satisfied}}
+ // expected-note at -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 at -22 {{candidate function}}
+ // expected-note at -4 {{candidate function}}
+B<> b5 = {Ambiguous{}}; // expected-error {{call to constructor of 'B<>' is ambiguous}}
+ // expected-note at -25 {{candidate function}}
+ // expected-note at -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 at -2 {{explicitly marked deleted here}}
+B<> b7 = {DeletedConversion{}}; // expected-error {{conversion function from 'DeletedConversion' to 'B<>' invokes a deleted function}}
+ // expected-note at -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 at -2 {{implicitly declared private here}}
+
+struct NoConv {};
+B<> b9(NoConv{}); // expected-error {{no matching constructor for initialization of 'B<>'}}
+ // expected-note at -42 {{constraints not satisfied}}
+ // expected-note at -43 {{because '0 != 0' (0 != 0) evaluated to false}}
+B<> b10 = {NoConv{}}; // expected-error {{no matching constructor for initialization of 'B<>'}}
+ // expected-note at -45 {{constraints not satisfied}}
+ // expected-note at -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 at -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 at -48 {{cannot convert initializer list argument to 'B<>'}}
+#endif
+} // namespace second_phase
More information about the cfe-commits
mailing list