[clang] CWG2327: Copy elision for direct-initialization with a conversion fun… (PR #232978)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 16 16:10:08 PDT 2026
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
More information about the cfe-commits
mailing list