[clang] [clang] Bugfix for choosing the more specialized overload (PR #83279)
Botond István Horváth via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 4 05:11:31 PST 2024
https://github.com/HoBoIs updated https://github.com/llvm/llvm-project/pull/83279
>From 68200ecf3267d1b3940fa73c25c50ee706932a98 Mon Sep 17 00:00:00 2001
From: Botond Istvan Horvath <horvath.botond.istvan at gmail.com>
Date: Wed, 28 Feb 2024 13:09:15 +0100
Subject: [PATCH 1/7] Bugfix for choosing the more specialized overload
There was a bug in clang where it couldn't choose which overload candidate is
more specialized if it was comparing a member-function to a non-member
function. Previously, this was detected as an ambigouity, now clang chooses correctly.
This patch fixes the bug by fully implementing CWG2445 and moving the template
transformation described in [temp.func.order] paragraph 3 from
isAtLeastAsSpecializedAs to Sema::getMoreSpecializedTemplate so we have the
transformed parameter list during the whole comperrassion. Also, to be able
to add the correct type for the implicit object parameter
Sema::getMoreSpecializedTemplate has new parameters for the object type.
---
clang/include/clang/Sema/Sema.h | 4 +-
clang/lib/Sema/SemaOverload.cpp | 12 +-
clang/lib/Sema/SemaTemplateDeduction.cpp | 263 +++++++++++++++--------
3 files changed, 186 insertions(+), 93 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index ef4b93fac95ce5..1a2a3a6bebd95e 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -9463,7 +9463,9 @@ class Sema final {
FunctionTemplateDecl *getMoreSpecializedTemplate(
FunctionTemplateDecl *FT1, FunctionTemplateDecl *FT2, SourceLocation Loc,
TemplatePartialOrderingContext TPOC, unsigned NumCallArguments1,
- unsigned NumCallArguments2, bool Reversed = false);
+ unsigned NumCallArguments2, QualType ObjType1 = {},
+ QualType ObjType2 = {}, bool Reversed = false);
+
UnresolvedSetIterator
getMostSpecialized(UnresolvedSetIterator SBegin, UnresolvedSetIterator SEnd,
TemplateSpecCandidateSet &FailedCandidates,
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 7d38043890ca20..60138236abf1d8 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -10526,14 +10526,24 @@ bool clang::isBetterOverloadCandidate(
// according to the partial ordering rules described in 14.5.5.2, or,
// if not that,
if (Cand1IsSpecialization && Cand2IsSpecialization) {
+ const auto *ObjContext1 =
+ dyn_cast<CXXRecordDecl>(Cand1.FoundDecl->getDeclContext());
+ const auto *ObjContext2 =
+ dyn_cast<CXXRecordDecl>(Cand2.FoundDecl->getDeclContext());
if (FunctionTemplateDecl *BetterTemplate = S.getMoreSpecializedTemplate(
Cand1.Function->getPrimaryTemplate(),
Cand2.Function->getPrimaryTemplate(), Loc,
isa<CXXConversionDecl>(Cand1.Function) ? TPOC_Conversion
: TPOC_Call,
Cand1.ExplicitCallArguments, Cand2.ExplicitCallArguments,
- Cand1.isReversed() ^ Cand2.isReversed()))
+ ObjContext1 ? QualType(ObjContext1->getTypeForDecl(), 0)
+ : QualType{},
+ ObjContext2 ? QualType(ObjContext2->getTypeForDecl(), 0)
+ : QualType{},
+ Cand1.isReversed() ^ Cand2.isReversed())) {
return BetterTemplate == Cand1.Function->getPrimaryTemplate();
+ }
+
}
// -— F1 and F2 are non-template functions with the same
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 563491f76f5478..2af3c29ae1f1c4 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -5333,11 +5333,31 @@ bool Sema::CheckIfFunctionSpecializationIsImmediate(FunctionDecl *FD,
return false;
}
+static QualType GetImplicitObjectParameterTypeCXX20(ASTContext &Context,
+ const CXXMethodDecl *Method,
+ QualType rawType,
+ bool isOtherRvr) {
+ // C++20 [temp.func.order]p3.1, p3.2:
+ //- The type X(M ) is “rvalue reference to cv A” if the optional ref-qualifier
+ // of M is && or if M has no ref-qualifier and the positionally-corresponding
+ // parameter of the other transformed template has rvalue reference type;
+ // if this determination depends recursively upon whether X(M ) is an rvalue
+ // reference type, it is not considered to have rvalue reference type.
+ //- Otherwise, X(M ) is “lvalue reference to cv A”.
+ assert(Method && !Method->isExplicitObjectMemberFunction() &&
+ "expected a member function with no explicit object parameter");
+
+ rawType = Context.getQualifiedType(rawType, Method->getMethodQualifiers());
+ if (Method->getRefQualifier() == RQ_RValue ||
+ (isOtherRvr && Method->getRefQualifier() == RQ_None))
+ return Context.getRValueReferenceType(rawType);
+ return Context.getLValueReferenceType(rawType);
+}
+
/// If this is a non-static member function,
-static void
-AddImplicitObjectParameterType(ASTContext &Context,
- CXXMethodDecl *Method,
- SmallVectorImpl<QualType> &ArgTypes) {
+static QualType GetImplicitObjectParameterType(ASTContext &Context,
+ const CXXMethodDecl *Method,
+ QualType rawType) {
// C++11 [temp.func.order]p3:
// [...] The new parameter is of type "reference to cv A," where cv are
// the cv-qualifiers of the function template (if any) and A is
@@ -5347,24 +5367,21 @@ AddImplicitObjectParameterType(ASTContext &Context,
// reference type based on [over.match.funcs]p4.
assert(Method && Method->isImplicitObjectMemberFunction() &&
"expected an implicit objet function");
- QualType ArgTy = Context.getTypeDeclType(Method->getParent());
- ArgTy = Context.getQualifiedType(ArgTy, Method->getMethodQualifiers());
+ rawType = Context.getQualifiedType(rawType, Method->getMethodQualifiers());
if (Method->getRefQualifier() == RQ_RValue)
- ArgTy = Context.getRValueReferenceType(ArgTy);
- else
- ArgTy = Context.getLValueReferenceType(ArgTy);
- ArgTypes.push_back(ArgTy);
+ return Context.getRValueReferenceType(rawType);
+ return Context.getLValueReferenceType(rawType);
}
/// Determine whether the function template \p FT1 is at least as
/// specialized as \p FT2.
-static bool isAtLeastAsSpecializedAs(Sema &S,
- SourceLocation Loc,
- FunctionTemplateDecl *FT1,
- FunctionTemplateDecl *FT2,
+static bool isAtLeastAsSpecializedAs(Sema &S, SourceLocation Loc,
+ const FunctionTemplateDecl *FT1,
+ const FunctionTemplateDecl *FT2,
TemplatePartialOrderingContext TPOC,
- unsigned NumCallArguments1,
- bool Reversed) {
+ bool Reversed,
+ const SmallVector<QualType, 4> &Args1,
+ const SmallVector<QualType, 4> &Args2) {
assert(!Reversed || TPOC == TPOC_Call);
FunctionDecl *FD1 = FT1->getTemplatedDecl();
@@ -5381,66 +5398,8 @@ static bool isAtLeastAsSpecializedAs(Sema &S,
// The types used to determine the ordering depend on the context in which
// the partial ordering is done:
TemplateDeductionInfo Info(Loc);
- SmallVector<QualType, 4> Args2;
switch (TPOC) {
- case TPOC_Call: {
- // - In the context of a function call, the function parameter types are
- // used.
- CXXMethodDecl *Method1 = dyn_cast<CXXMethodDecl>(FD1);
- CXXMethodDecl *Method2 = dyn_cast<CXXMethodDecl>(FD2);
-
- // C++11 [temp.func.order]p3:
- // [...] If only one of the function templates is a non-static
- // member, that function template is considered to have a new
- // first parameter inserted in its function parameter list. The
- // new parameter is of type "reference to cv A," where cv are
- // the cv-qualifiers of the function template (if any) and A is
- // the class of which the function template is a member.
- //
- // Note that we interpret this to mean "if one of the function
- // templates is a non-static member and the other is a non-member";
- // otherwise, the ordering rules for static functions against non-static
- // functions don't make any sense.
- //
- // C++98/03 doesn't have this provision but we've extended DR532 to cover
- // it as wording was broken prior to it.
- SmallVector<QualType, 4> Args1;
-
- unsigned NumComparedArguments = NumCallArguments1;
-
- if (!Method2 && Method1 && Method1->isImplicitObjectMemberFunction()) {
- // Compare 'this' from Method1 against first parameter from Method2.
- AddImplicitObjectParameterType(S.Context, Method1, Args1);
- ++NumComparedArguments;
- } else if (!Method1 && Method2 &&
- Method2->isImplicitObjectMemberFunction()) {
- // Compare 'this' from Method2 against first parameter from Method1.
- AddImplicitObjectParameterType(S.Context, Method2, Args2);
- } else if (Method1 && Method2 && Reversed &&
- Method1->isImplicitObjectMemberFunction() &&
- Method2->isImplicitObjectMemberFunction()) {
- // Compare 'this' from Method1 against second parameter from Method2
- // and 'this' from Method2 against second parameter from Method1.
- AddImplicitObjectParameterType(S.Context, Method1, Args1);
- AddImplicitObjectParameterType(S.Context, Method2, Args2);
- ++NumComparedArguments;
- }
-
- Args1.insert(Args1.end(), Proto1->param_type_begin(),
- Proto1->param_type_end());
- Args2.insert(Args2.end(), Proto2->param_type_begin(),
- Proto2->param_type_end());
-
- // C++ [temp.func.order]p5:
- // The presence of unused ellipsis and default arguments has no effect on
- // the partial ordering of function templates.
- if (Args1.size() > NumComparedArguments)
- Args1.resize(NumComparedArguments);
- if (Args2.size() > NumComparedArguments)
- Args2.resize(NumComparedArguments);
- if (Reversed)
- std::reverse(Args2.begin(), Args2.end());
-
+ case TPOC_Call:
if (DeduceTemplateArguments(S, TemplateParams, Args2.data(), Args2.size(),
Args1.data(), Args1.size(), Info, Deduced,
TDF_None, /*PartialOrdering=*/true) !=
@@ -5448,7 +5407,6 @@ static bool isAtLeastAsSpecializedAs(Sema &S,
return false;
break;
- }
case TPOC_Conversion:
// - In the context of a call to a conversion operator, the return types
@@ -5539,6 +5497,14 @@ static bool isAtLeastAsSpecializedAs(Sema &S,
/// \param NumCallArguments2 The number of arguments in the call to FT2, used
/// only when \c TPOC is \c TPOC_Call.
///
+/// \param RawObjType1 The type of the object parameter of FT1 if a member
+/// function only used if \c TPOC is \c TPOC_Call and FT1 is a Function
+/// template from a member function
+///
+/// \param RawObjType2 The type of the object parameter of FT2 if a member
+/// function only used if \c TPOC is \c TPOC_Call and FT2 is a Function
+/// template from a member function
+///
/// \param Reversed If \c true, exactly one of FT1 and FT2 is an overload
/// candidate with a reversed parameter order. In this case, the corresponding
/// P/A pairs between FT1 and FT2 are reversed.
@@ -5548,13 +5514,113 @@ static bool isAtLeastAsSpecializedAs(Sema &S,
FunctionTemplateDecl *Sema::getMoreSpecializedTemplate(
FunctionTemplateDecl *FT1, FunctionTemplateDecl *FT2, SourceLocation Loc,
TemplatePartialOrderingContext TPOC, unsigned NumCallArguments1,
- unsigned NumCallArguments2, bool Reversed) {
+ unsigned NumCallArguments2, QualType RawObjType1, QualType RawObjType2,
+ bool Reversed) {
+ SmallVector<QualType, 4> Args1;
+ SmallVector<QualType, 4> Args2;
+ const FunctionDecl *FD1 = FT1->getTemplatedDecl();
+ const FunctionDecl *FD2 = FT2->getTemplatedDecl();
+ bool shouldConvert1 = false;
+ bool shouldConvert2 = false;
+ QualType ObjType1;
+ QualType ObjType2;
+ if (TPOC == TPOC_Call) {
+ const FunctionProtoType *Proto1 =
+ FD1->getType()->getAs<FunctionProtoType>();
+ const FunctionProtoType *Proto2 =
+ FD2->getType()->getAs<FunctionProtoType>();
- bool Better1 = isAtLeastAsSpecializedAs(*this, Loc, FT1, FT2, TPOC,
- NumCallArguments1, Reversed);
- bool Better2 = isAtLeastAsSpecializedAs(*this, Loc, FT2, FT1, TPOC,
- NumCallArguments2, Reversed);
+ // - In the context of a function call, the function parameter types are
+ // used.
+ const CXXMethodDecl *Method1 = dyn_cast<CXXMethodDecl>(FD1);
+ const CXXMethodDecl *Method2 = dyn_cast<CXXMethodDecl>(FD2);
+
+ if (getLangOpts().CPlusPlus20) {
+ // C++20 [temp.func.order]p3
+ // [...] Each function template M that is a member function is
+ // considered to have a new first parameter of type
+ // X(M), described below, inserted in its function parameter list.
+ //
+ // Note that we interpret "that is a member function" as
+ // "that is a member function with no expicit object argument".
+ // Otherwise the ordering rules for methods with expicit objet arguments
+ // against anything else make no sense.
+ shouldConvert1 = Method1 && !Method1->isExplicitObjectMemberFunction();
+ shouldConvert2 = Method2 && !Method2->isExplicitObjectMemberFunction();
+ if (shouldConvert1) {
+ bool isR2 =
+ Method2 && !Method2->isExplicitObjectMemberFunction()
+ ? Method2->getRefQualifier() == RQ_RValue
+ : Proto2->param_type_begin()[0]->isRValueReferenceType();
+ // Compare 'this' from Method1 against first parameter from Method2.
+ ObjType1 = GetImplicitObjectParameterTypeCXX20(this->Context, Method1,
+ RawObjType1, isR2);
+ Args1.push_back(ObjType1);
+ }
+ if (shouldConvert2) {
+ bool isR1 =
+ Method1 && !Method1->isExplicitObjectMemberFunction()
+ ? Method1->getRefQualifier() == RQ_RValue
+ : Proto1->param_type_begin()[0]->isRValueReferenceType();
+ // Compare 'this' from Method2 against first parameter from Method1.
+ ObjType2 = GetImplicitObjectParameterTypeCXX20(this->Context, Method2,
+ RawObjType2, isR1);
+ Args2.push_back(ObjType2);
+ }
+ } else {
+ // C++11 [temp.func.order]p3:
+ // [...] If only one of the function templates is a non-static
+ // member, that function template is considered to have a new
+ // first parameter inserted in its function parameter list. The
+ // new parameter is of type "reference to cv A," where cv are
+ // the cv-qualifiers of the function template (if any) and A is
+ // the class of which the function template is a member.
+ //
+ // Note that we interpret this to mean "if one of the function
+ // templates is a non-static member and the other is a non-member";
+ // otherwise, the ordering rules for static functions against non-static
+ // functions don't make any sense.
+ //
+ // C++98/03 doesn't have this provision but we've extended DR532 to cover
+ // it as wording was broken prior to it.
+ shouldConvert1 =
+ !Method2 && Method1 && Method1->isImplicitObjectMemberFunction();
+ shouldConvert2 =
+ !Method1 && Method2 && Method2->isImplicitObjectMemberFunction();
+ if (shouldConvert1) {
+ // Compare 'this' from Method1 against first parameter from Method2.
+ ObjType1 =
+ GetImplicitObjectParameterType(this->Context, Method1, RawObjType1);
+ Args1.push_back(ObjType1);
+ }
+ if (shouldConvert2) {
+ // Compare 'this' from Method2 against first parameter from Method1.
+ ObjType2 =
+ GetImplicitObjectParameterType(this->Context, Method2, RawObjType2);
+ Args2.push_back(ObjType2);
+ }
+ }
+ unsigned NumComparedArguments = NumCallArguments1 + shouldConvert1;
+
+ Args1.insert(Args1.end(), Proto1->param_type_begin(),
+ Proto1->param_type_end());
+ Args2.insert(Args2.end(), Proto2->param_type_begin(),
+ Proto2->param_type_end());
+ // C++ [temp.func.order]p5:
+ // The presence of unused ellipsis and default arguments has no effect on
+ // the partial ordering of function templates.
+ if (Args1.size() > NumComparedArguments)
+ Args1.resize(NumComparedArguments);
+ if (Args2.size() > NumComparedArguments)
+ Args2.resize(NumComparedArguments);
+ if (Reversed)
+ std::reverse(Args2.begin(), Args2.end());
+ }
+ bool Better1 = isAtLeastAsSpecializedAs(*this, Loc, FT1, FT2, TPOC, Reversed,
+ Args1, Args2);
+ bool Better2 = isAtLeastAsSpecializedAs(*this, Loc, FT2, FT1, TPOC, Reversed,
+ Args2, Args1);
// C++ [temp.deduct.partial]p10:
// F is more specialized than G if F is at least as specialized as G and G
// is not at least as specialized as F.
@@ -5568,12 +5634,28 @@ FunctionTemplateDecl *Sema::getMoreSpecializedTemplate(
// ... and if G has a trailing function parameter pack for which F does not
// have a corresponding parameter, and if F does not have a trailing
// function parameter pack, then F is more specialized than G.
- FunctionDecl *FD1 = FT1->getTemplatedDecl();
- FunctionDecl *FD2 = FT2->getTemplatedDecl();
- unsigned NumParams1 = FD1->getNumParams();
- unsigned NumParams2 = FD2->getNumParams();
- bool Variadic1 = NumParams1 && FD1->parameters().back()->isParameterPack();
- bool Variadic2 = NumParams2 && FD2->parameters().back()->isParameterPack();
+
+ SmallVector<QualType> param1;
+ param1.reserve(FD1->param_size() + shouldConvert1);
+ if (shouldConvert1)
+ param1.push_back(ObjType1);
+ for (const auto &x : FD1->parameters())
+ param1.push_back(x->getType());
+
+ SmallVector<QualType> param2;
+ param2.reserve(FD2->param_size() + shouldConvert2);
+ if (shouldConvert2)
+ param2.push_back(ObjType2);
+ for (const auto &x : FD2->parameters())
+ param2.push_back(x->getType());
+
+ unsigned NumParams1 = param1.size();
+ unsigned NumParams2 = param2.size();
+
+ bool Variadic1 =
+ FD1->param_size() && FD1->parameters().back()->isParameterPack();
+ bool Variadic2 =
+ FD2->param_size() && FD2->parameters().back()->isParameterPack();
if (Variadic1 != Variadic2) {
if (Variadic1 && NumParams1 > NumParams2)
return FT2;
@@ -5584,8 +5666,8 @@ FunctionTemplateDecl *Sema::getMoreSpecializedTemplate(
// This a speculative fix for CWG1432 (Similar to the fix for CWG1395) that
// there is no wording or even resolution for this issue.
for (int i = 0, e = std::min(NumParams1, NumParams2); i < e; ++i) {
- QualType T1 = FD1->getParamDecl(i)->getType().getCanonicalType();
- QualType T2 = FD2->getParamDecl(i)->getType().getCanonicalType();
+ QualType T1 = param1[i].getCanonicalType();
+ QualType T2 = param2[i].getCanonicalType();
auto *TST1 = dyn_cast<TemplateSpecializationType>(T1);
auto *TST2 = dyn_cast<TemplateSpecializationType>(T2);
if (!TST1 || !TST2)
@@ -5644,8 +5726,7 @@ FunctionTemplateDecl *Sema::getMoreSpecializedTemplate(
// Any top-level cv-qualifiers modifying a parameter type are deleted when
// forming the function type.
for (unsigned i = 0; i < NumParams1; ++i)
- if (!Context.hasSameUnqualifiedType(FD1->getParamDecl(i)->getType(),
- FD2->getParamDecl(i)->getType()))
+ if (!Context.hasSameUnqualifiedType(param1[i], param2[i]))
return nullptr;
// C++20 [temp.func.order]p6.3:
>From 3cb51452a25227afb0750ae65fcca9bf281433ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Botond=20Istv=C3=A1n=20Horv=C3=A1th?=
<56926027+HoBoIs at users.noreply.github.com>
Date: Wed, 28 Feb 2024 16:50:30 +0100
Subject: [PATCH 2/7] Formatting SemaOverload.cpp
---
clang/lib/Sema/SemaOverload.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 60138236abf1d8..74d714ad308af1 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -10543,7 +10543,6 @@ bool clang::isBetterOverloadCandidate(
Cand1.isReversed() ^ Cand2.isReversed())) {
return BetterTemplate == Cand1.Function->getPrimaryTemplate();
}
-
}
// -— F1 and F2 are non-template functions with the same
>From 54e7fb1ae193196538423a0c1cbb62fc518f77ef Mon Sep 17 00:00:00 2001
From: Botond Istvan Horvath <horvath.botond.istvan at gmail.com>
Date: Wed, 28 Feb 2024 18:46:07 +0100
Subject: [PATCH 3/7] Unified the branches for newer and older standards
---
clang/lib/Sema/SemaTemplateDeduction.cpp | 97 +++++++++---------------
1 file changed, 37 insertions(+), 60 deletions(-)
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 2af3c29ae1f1c4..f7526933be12f4 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -5333,10 +5333,10 @@ bool Sema::CheckIfFunctionSpecializationIsImmediate(FunctionDecl *FD,
return false;
}
-static QualType GetImplicitObjectParameterTypeCXX20(ASTContext &Context,
- const CXXMethodDecl *Method,
- QualType rawType,
- bool isOtherRvr) {
+static QualType GetImplicitObjectParameterType(ASTContext &Context,
+ const CXXMethodDecl *Method,
+ QualType RawType,
+ bool IsOtherRvr) {
// C++20 [temp.func.order]p3.1, p3.2:
//- The type X(M ) is “rvalue reference to cv A” if the optional ref-qualifier
// of M is && or if M has no ref-qualifier and the positionally-corresponding
@@ -5344,20 +5344,7 @@ static QualType GetImplicitObjectParameterTypeCXX20(ASTContext &Context,
// if this determination depends recursively upon whether X(M ) is an rvalue
// reference type, it is not considered to have rvalue reference type.
//- Otherwise, X(M ) is “lvalue reference to cv A”.
- assert(Method && !Method->isExplicitObjectMemberFunction() &&
- "expected a member function with no explicit object parameter");
-
- rawType = Context.getQualifiedType(rawType, Method->getMethodQualifiers());
- if (Method->getRefQualifier() == RQ_RValue ||
- (isOtherRvr && Method->getRefQualifier() == RQ_None))
- return Context.getRValueReferenceType(rawType);
- return Context.getLValueReferenceType(rawType);
-}
-
-/// If this is a non-static member function,
-static QualType GetImplicitObjectParameterType(ASTContext &Context,
- const CXXMethodDecl *Method,
- QualType rawType) {
+ //
// C++11 [temp.func.order]p3:
// [...] The new parameter is of type "reference to cv A," where cv are
// the cv-qualifiers of the function template (if any) and A is
@@ -5365,12 +5352,15 @@ static QualType GetImplicitObjectParameterType(ASTContext &Context,
//
// The standard doesn't say explicitly, but we pick the appropriate kind of
// reference type based on [over.match.funcs]p4.
- assert(Method && Method->isImplicitObjectMemberFunction() &&
- "expected an implicit objet function");
- rawType = Context.getQualifiedType(rawType, Method->getMethodQualifiers());
- if (Method->getRefQualifier() == RQ_RValue)
- return Context.getRValueReferenceType(rawType);
- return Context.getLValueReferenceType(rawType);
+
+ assert(Method && !Method->isExplicitObjectMemberFunction() &&
+ "expected a member function with no explicit object parameter");
+
+ RawType = Context.getQualifiedType(RawType, Method->getMethodQualifiers());
+ if (Method->getRefQualifier() == RQ_RValue ||
+ (IsOtherRvr && Method->getRefQualifier() == RQ_None))
+ return Context.getRValueReferenceType(RawType);
+ return Context.getLValueReferenceType(RawType);
}
/// Determine whether the function template \p FT1 is at least as
@@ -5547,34 +5537,11 @@ FunctionTemplateDecl *Sema::getMoreSpecializedTemplate(
// against anything else make no sense.
shouldConvert1 = Method1 && !Method1->isExplicitObjectMemberFunction();
shouldConvert2 = Method2 && !Method2->isExplicitObjectMemberFunction();
- if (shouldConvert1) {
- bool isR2 =
- Method2 && !Method2->isExplicitObjectMemberFunction()
- ? Method2->getRefQualifier() == RQ_RValue
- : Proto2->param_type_begin()[0]->isRValueReferenceType();
- // Compare 'this' from Method1 against first parameter from Method2.
- ObjType1 = GetImplicitObjectParameterTypeCXX20(this->Context, Method1,
- RawObjType1, isR2);
- Args1.push_back(ObjType1);
- }
- if (shouldConvert2) {
- bool isR1 =
- Method1 && !Method1->isExplicitObjectMemberFunction()
- ? Method1->getRefQualifier() == RQ_RValue
- : Proto1->param_type_begin()[0]->isRValueReferenceType();
- // Compare 'this' from Method2 against first parameter from Method1.
- ObjType2 = GetImplicitObjectParameterTypeCXX20(this->Context, Method2,
- RawObjType2, isR1);
- Args2.push_back(ObjType2);
- }
} else {
// C++11 [temp.func.order]p3:
// [...] If only one of the function templates is a non-static
// member, that function template is considered to have a new
- // first parameter inserted in its function parameter list. The
- // new parameter is of type "reference to cv A," where cv are
- // the cv-qualifiers of the function template (if any) and A is
- // the class of which the function template is a member.
+ // first parameter inserted in its function parameter list.
//
// Note that we interpret this to mean "if one of the function
// templates is a non-static member and the other is a non-member";
@@ -5587,18 +5554,28 @@ FunctionTemplateDecl *Sema::getMoreSpecializedTemplate(
!Method2 && Method1 && Method1->isImplicitObjectMemberFunction();
shouldConvert2 =
!Method1 && Method2 && Method2->isImplicitObjectMemberFunction();
- if (shouldConvert1) {
- // Compare 'this' from Method1 against first parameter from Method2.
- ObjType1 =
- GetImplicitObjectParameterType(this->Context, Method1, RawObjType1);
- Args1.push_back(ObjType1);
- }
- if (shouldConvert2) {
- // Compare 'this' from Method2 against first parameter from Method1.
- ObjType2 =
- GetImplicitObjectParameterType(this->Context, Method2, RawObjType2);
- Args2.push_back(ObjType2);
- }
+ }
+ if (shouldConvert1) {
+ bool isR2 =
+ getLangOpts().CPlusPlus20 &&
+ (shouldConvert1
+ ? Method2->getRefQualifier() == RQ_RValue
+ : Proto2->param_type_begin()[0]->isRValueReferenceType());
+ // Compare 'this' from Method1 against first parameter from Method2.
+ ObjType1 = GetImplicitObjectParameterType(this->Context, Method1,
+ RawObjType1, isR2);
+ Args1.push_back(ObjType1);
+ }
+ if (shouldConvert2) {
+ bool isR1 =
+ getLangOpts().CPlusPlus20 &&
+ (shouldConvert2
+ ? Method1->getRefQualifier() == RQ_RValue
+ : Proto1->param_type_begin()[0]->isRValueReferenceType());
+ // Compare 'this' from Method2 against first parameter from Method1.
+ ObjType2 = GetImplicitObjectParameterType(this->Context, Method2,
+ RawObjType2, isR1);
+ Args2.push_back(ObjType2);
}
unsigned NumComparedArguments = NumCallArguments1 + shouldConvert1;
>From e377e6e320d5c280e9364bbd14be456aab57ea46 Mon Sep 17 00:00:00 2001
From: Botond Istvan Horvath <horvath.botond.istvan at gmail.com>
Date: Thu, 29 Feb 2024 15:35:12 +0100
Subject: [PATCH 4/7] minor fix + tests + release notes
---
clang/docs/ReleaseNotes.rst | 6 ++++
clang/lib/Sema/SemaTemplateDeduction.cpp | 12 +++----
clang/test/SemaCXX/overload-template.cpp | 25 +++++++++++++++
clang/test/SemaCXX/overloaded-operator.cpp | 37 ++++++++++++++++++++++
4 files changed, 74 insertions(+), 6 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 7e16b9f0c67dbd..6a1b93db9f6486 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -290,6 +290,12 @@ Bug Fixes to C++ Support
lookup searches the bases of an incomplete class.
- Fix a crash when an unresolved overload set is encountered on the RHS of a ``.*`` operator.
(`#53815 <https://github.com/llvm/llvm-project/issues/53815>`_)
+- Fix a bug where overload resolution falsly reported an ambiguity when it was compearing
+ a member-function against a non member function or a member-function with an
+ explicit object parameter against a member function with no explicit object parameter
+ when one of the function had more specialized templates.
+ Fixes (`#82509 <https://github.com/llvm/llvm-project/issues/82509>`_)
+ and (`#74494 <https://github.com/llvm/llvm-project/issues/74494>`_)
Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index f7526933be12f4..04e46ab69876f6 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -5556,25 +5556,25 @@ FunctionTemplateDecl *Sema::getMoreSpecializedTemplate(
!Method1 && Method2 && Method2->isImplicitObjectMemberFunction();
}
if (shouldConvert1) {
- bool isR2 =
+ bool isRValRef2 =
getLangOpts().CPlusPlus20 &&
- (shouldConvert1
+ (shouldConvert2
? Method2->getRefQualifier() == RQ_RValue
: Proto2->param_type_begin()[0]->isRValueReferenceType());
// Compare 'this' from Method1 against first parameter from Method2.
ObjType1 = GetImplicitObjectParameterType(this->Context, Method1,
- RawObjType1, isR2);
+ RawObjType1, isRValRef2);
Args1.push_back(ObjType1);
}
if (shouldConvert2) {
- bool isR1 =
+ bool isRValRef1 =
getLangOpts().CPlusPlus20 &&
- (shouldConvert2
+ (shouldConvert1
? Method1->getRefQualifier() == RQ_RValue
: Proto1->param_type_begin()[0]->isRValueReferenceType());
// Compare 'this' from Method2 against first parameter from Method1.
ObjType2 = GetImplicitObjectParameterType(this->Context, Method2,
- RawObjType2, isR1);
+ RawObjType2, isRValRef1);
Args2.push_back(ObjType2);
}
unsigned NumComparedArguments = NumCallArguments1 + shouldConvert1;
diff --git a/clang/test/SemaCXX/overload-template.cpp b/clang/test/SemaCXX/overload-template.cpp
index 0a23788ef3da6a..0fe13c479cce22 100644
--- a/clang/test/SemaCXX/overload-template.cpp
+++ b/clang/test/SemaCXX/overload-template.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++23 -verify -fsyntax-only %s
enum copy_traits { movable = 1 };
@@ -33,3 +34,27 @@ void ReproducesBugSimply() {
InsertRow(3, B{}); // expected-error {{no matching function for call to 'InsertRow'}}
}
+#if __cplusplus >= 202302L
+namespace overloadCheck{
+ template<typename T>
+ concept AlwaysTrue = true;
+
+ struct S {
+ int f(AlwaysTrue auto) { return 1; }
+ void f(this S&&, auto) {}
+
+ void g(auto) {}
+ int g(this S&&,AlwaysTrue auto) {return 1;}
+
+ int h(AlwaysTrue auto) { return 1; } //expected-note {{previous definition is here}}
+ int h(this S&&,AlwaysTrue auto) { // expected-error {{class member cannot be redeclared}}
+ return 1;
+ }
+ };
+
+ int main() {
+ int x = S{}.f(0);
+ int y = S{}.g(0);
+ }
+}
+#endif
diff --git a/clang/test/SemaCXX/overloaded-operator.cpp b/clang/test/SemaCXX/overloaded-operator.cpp
index 887848c29b83c5..49311625d7ab2d 100644
--- a/clang/test/SemaCXX/overloaded-operator.cpp
+++ b/clang/test/SemaCXX/overloaded-operator.cpp
@@ -645,3 +645,40 @@ class b {
}
+
+#if __cplusplus >= 202002L
+namespace nw{
+ template<class T>
+ concept AlwaysTrue=true;
+
+ struct S{
+ template<class T>
+ void operator+(const T&)const{}
+
+ template<AlwaysTrue T>
+ int operator-(const T&)const{return 0;}
+
+ template<AlwaysTrue T>
+ int operator*(const T&)const{ // expected-note {{candidate function}}
+ return 0;
+ }
+ };
+
+ template<AlwaysTrue T>
+ int operator+(const S&, const T&){return 0;}
+
+ template<class T>
+ void operator-(const S&, const T&){}
+
+ template<AlwaysTrue T>
+ int operator*(const S&, const T&){ // expected-note {{candidate function}}
+ return 0;
+ }
+
+ void foo(){
+ int a = S{} + 1;
+ int b = S{} - 1;
+ int c = S{} * 1; // expected-error {{use of overloaded operator '*' is ambiguous (with operand types 'S' and 'int')}}
+ }
+}
+#endif
>From 74e69bf423645f98e342710f8e251365cdb7cd3b Mon Sep 17 00:00:00 2001
From: Botond Istvan Horvath <horvath.botond.istvan at gmail.com>
Date: Fri, 1 Mar 2024 14:40:35 +0100
Subject: [PATCH 5/7] Set DR CWG2445 status
---
clang/www/cxx_dr_status.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 8b638e06f4aab6..5973d825433463 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -14478,7 +14478,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2445.html">2445</a></td>
<td>C++20</td>
<td>Partial ordering with rewritten candidates</td>
- <td class="unknown" align="center">Unknown</td>
+ <td class="unreleased" align="center">Clang 18</td>
</tr>
<tr id="2446">
<td><a href="https://cplusplus.github.io/CWG/issues/2446.html">2446</a></td>
>From d8c5aa26f7213e98f8030e1613efd5136b44b1a5 Mon Sep 17 00:00:00 2001
From: Botond Istvan Horvath <horvath.botond.istvan at gmail.com>
Date: Fri, 1 Mar 2024 16:38:24 +0100
Subject: [PATCH 6/7] Added test to dr24xx.cpp, auto-generated
cxx_dr_status.html
---
clang/test/CXX/drs/dr24xx.cpp | 32 ++++++++++++++++++++++++++++++++
clang/www/cxx_dr_status.html | 2 +-
2 files changed, 33 insertions(+), 1 deletion(-)
diff --git a/clang/test/CXX/drs/dr24xx.cpp b/clang/test/CXX/drs/dr24xx.cpp
index ae8dda3351f48e..e756f43985517b 100644
--- a/clang/test/CXX/drs/dr24xx.cpp
+++ b/clang/test/CXX/drs/dr24xx.cpp
@@ -68,3 +68,35 @@ template<A> struct X {};
X<1> x;
#endif
}
+namespace dr2445 { // dr2445: 19
+#if __cplusplus >= 202002L
+ template <typename> constexpr bool F = false;
+ template <typename T> struct A { };
+
+ template <typename T, typename U>
+ bool operator==(T, A<U *>);
+
+ template <typename T, typename U>
+ bool operator!=(A<T>, U) {
+ static_assert(F<T>, "Isn't this less specialized?");
+ return false;
+ }
+
+ bool f(A<int> ax, A<int *> ay) { return ay != ax; }
+
+ template<class T> concept AlwaysTrue=true;
+ template <class T> struct B {
+ template <AlwaysTrue U>
+ bool operator==(const B<U>&)const;
+ };
+
+
+ template <typename U>
+ bool operator==(const B<int>&,const B<U>&) {
+ static_assert(F<int>, "Isn't this less specialized?");
+ return false;
+ }
+
+ bool g(B<int> bx, B<int *> by) { return bx == by; }
+#endif
+}
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 5973d825433463..7f288a084edf81 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -14478,7 +14478,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2445.html">2445</a></td>
<td>C++20</td>
<td>Partial ordering with rewritten candidates</td>
- <td class="unreleased" align="center">Clang 18</td>
+ <td class="unreleased" align="center">Clang 19</td>
</tr>
<tr id="2446">
<td><a href="https://cplusplus.github.io/CWG/issues/2446.html">2446</a></td>
>From d70ad7da5228383ff761759629b0430e2e4106eb Mon Sep 17 00:00:00 2001
From: Botond Istvan Horvath <horvath.botond.istvan at gmail.com>
Date: Mon, 4 Mar 2024 14:11:14 +0100
Subject: [PATCH 7/7] added tests for rvalref and volatile
---
clang/test/CXX/drs/dr24xx.cpp | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/clang/test/CXX/drs/dr24xx.cpp b/clang/test/CXX/drs/dr24xx.cpp
index e756f43985517b..4534ed26e56d07 100644
--- a/clang/test/CXX/drs/dr24xx.cpp
+++ b/clang/test/CXX/drs/dr24xx.cpp
@@ -68,6 +68,7 @@ template<A> struct X {};
X<1> x;
#endif
}
+
namespace dr2445 { // dr2445: 19
#if __cplusplus >= 202002L
template <typename> constexpr bool F = false;
@@ -98,5 +99,33 @@ namespace dr2445 { // dr2445: 19
}
bool g(B<int> bx, B<int *> by) { return bx == by; }
+
+ struct C{
+ template<AlwaysTrue T>
+ int operator+(T){return 0;}
+ template<class T>
+ void operator-(T){}
+ };
+ template<class T>
+ void operator+(C&&,T){}
+ template<AlwaysTrue T>
+ int operator-(C&&,T){return 0;}
+
+ void t(int* iptr){
+ int x1 = C{} + iptr;
+ int x2 = C{} - iptr;
+ }
+
+ struct D{
+ template<AlwaysTrue T>
+ int operator+(T) volatile {return 1;}
+ };
+
+ template<class T>
+ void operator+(volatile D&,T) {}
+
+ int foo(volatile D& d){
+ return d + 1;
+ }
#endif
}
More information about the cfe-commits
mailing list