Skip to content

[Clang] Correctly handle taking the address of an explicit object member function template #147046

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,8 @@ Bug Fixes to C++ Support
- Fixed a crash when constant evaluating some explicit object member assignment operators. (#GH142835)
- Fixed an access checking bug when substituting into concepts (#GH115838)
- Fix a bug where private access specifier of overloaded function not respected. (#GH107629)
- Correctly handles calling an explicit object member function template overload set
through its address (``(&Foo::bar<baz>)()``).

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -12500,6 +12500,7 @@ class Sema final : public SemaBase {
sema::TemplateDeductionInfo &Info,
SmallVectorImpl<OriginalCallArg> const *OriginalCallArgs,
bool PartialOverloading, bool PartialOrdering,
bool ForOverloadSetAddressResolution,
llvm::function_ref<bool(bool)> CheckNonDependent =
[](bool /*OnlyInitializeNonUserDefinedConversions*/) {
return false;
Expand Down
121 changes: 74 additions & 47 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7700,18 +7700,6 @@ void Sema::AddMethodCandidate(
EnterExpressionEvaluationContext Unevaluated(
*this, Sema::ExpressionEvaluationContext::Unevaluated);

// Add this candidate
OverloadCandidate &Candidate =
CandidateSet.addCandidate(Args.size() + 1, EarlyConversions);
Candidate.FoundDecl = FoundDecl;
Candidate.Function = Method;
Candidate.RewriteKind =
CandidateSet.getRewriteInfo().getRewriteKind(Method, PO);
Candidate.TookAddressOfOverload =
CandidateSet.getKind() == OverloadCandidateSet::CSK_AddressOfOverloadSet;
Candidate.ExplicitCallArguments = Args.size();
Candidate.StrictPackMatch = StrictPackMatch;

bool IgnoreExplicitObject =
(Method->isExplicitObjectMemberFunction() &&
CandidateSet.getKind() ==
Expand All @@ -7727,6 +7715,23 @@ void Sema::AddMethodCandidate(
unsigned NumParams = Method->getNumParams() - ExplicitOffset +
int(ImplicitObjectMethodTreatedAsStatic);

unsigned ExtraArgs =
CandidateSet.getKind() == OverloadCandidateSet::CSK_AddressOfOverloadSet
? 0
: 1;

// Add this candidate
OverloadCandidate &Candidate =
CandidateSet.addCandidate(Args.size() + ExtraArgs, EarlyConversions);
Candidate.FoundDecl = FoundDecl;
Candidate.Function = Method;
Candidate.RewriteKind =
CandidateSet.getRewriteInfo().getRewriteKind(Method, PO);
Candidate.TookAddressOfOverload =
CandidateSet.getKind() == OverloadCandidateSet::CSK_AddressOfOverloadSet;
Candidate.ExplicitCallArguments = Args.size();
Candidate.StrictPackMatch = StrictPackMatch;

// (C++ 13.3.2p2): A candidate function having fewer than m
// parameters is viable only if it has an ellipsis in its parameter
// list (8.3.5).
Expand Down Expand Up @@ -7757,29 +7762,31 @@ void Sema::AddMethodCandidate(
Candidate.Viable = true;

unsigned FirstConvIdx = PO == OverloadCandidateParamOrder::Reversed ? 1 : 0;
if (ObjectType.isNull())
Candidate.IgnoreObjectArgument = true;
else if (Method->isStatic()) {
// [over.best.ics.general]p8
// When the parameter is the implicit object parameter of a static member
// function, the implicit conversion sequence is a standard conversion
// sequence that is neither better nor worse than any other standard
// conversion sequence.
//
// This is a rule that was introduced in C++23 to support static lambdas. We
// apply it retroactively because we want to support static lambdas as an
// extension and it doesn't hurt previous code.
Candidate.Conversions[FirstConvIdx].setStaticObjectArgument();
} else {
// Determine the implicit conversion sequence for the object
// parameter.
Candidate.Conversions[FirstConvIdx] = TryObjectArgumentInitialization(
*this, CandidateSet.getLocation(), ObjectType, ObjectClassification,
Method, ActingContext, /*InOverloadResolution=*/true);
if (Candidate.Conversions[FirstConvIdx].isBad()) {
Candidate.Viable = false;
Candidate.FailureKind = ovl_fail_bad_conversion;
return;
if (!IgnoreExplicitObject) {
if (ObjectType.isNull())
Candidate.IgnoreObjectArgument = true;
else if (Method->isStatic()) {
// [over.best.ics.general]p8
// When the parameter is the implicit object parameter of a static member
// function, the implicit conversion sequence is a standard conversion
// sequence that is neither better nor worse than any other standard
// conversion sequence.
//
// This is a rule that was introduced in C++23 to support static lambdas.
// We apply it retroactively because we want to support static lambdas as
// an extension and it doesn't hurt previous code.
Candidate.Conversions[FirstConvIdx].setStaticObjectArgument();
} else {
// Determine the implicit conversion sequence for the object
// parameter.
Candidate.Conversions[FirstConvIdx] = TryObjectArgumentInitialization(
*this, CandidateSet.getLocation(), ObjectType, ObjectClassification,
Method, ActingContext, /*InOverloadResolution=*/true);
if (Candidate.Conversions[FirstConvIdx].isBad()) {
Candidate.Viable = false;
Candidate.FailureKind = ovl_fail_bad_conversion;
return;
}
}
}

Expand Down Expand Up @@ -7807,7 +7814,7 @@ void Sema::AddMethodCandidate(
// arguments.
for (unsigned ArgIdx = 0; ArgIdx < Args.size(); ++ArgIdx) {
unsigned ConvIdx =
PO == OverloadCandidateParamOrder::Reversed ? 0 : (ArgIdx + 1);
PO == OverloadCandidateParamOrder::Reversed ? 0 : (ArgIdx + ExtraArgs);
if (Candidate.Conversions[ConvIdx].isInitialized()) {
// We already formed a conversion sequence for this parameter during
// template argument deduction.
Expand Down Expand Up @@ -7876,6 +7883,7 @@ static void AddMethodTemplateCandidateImmediately(
// function template are combined with the set of non-template candidate
// functions.
TemplateDeductionInfo Info(CandidateSet.getLocation());
auto *Method = cast<CXXMethodDecl>(MethodTmpl->getTemplatedDecl());
FunctionDecl *Specialization = nullptr;
ConversionSequenceList Conversions;
if (TemplateDeductionResult Result = S.DeduceTemplateArguments(
Expand All @@ -7897,14 +7905,18 @@ static void AddMethodTemplateCandidateImmediately(
OverloadCandidate &Candidate =
CandidateSet.addCandidate(Conversions.size(), Conversions);
Candidate.FoundDecl = FoundDecl;
Candidate.Function = MethodTmpl->getTemplatedDecl();
Candidate.Function = Method;
Candidate.Viable = false;
Candidate.RewriteKind =
CandidateSet.getRewriteInfo().getRewriteKind(Candidate.Function, PO);
Candidate.IsSurrogate = false;
Candidate.TookAddressOfOverload =
CandidateSet.getKind() ==
OverloadCandidateSet::CSK_AddressOfOverloadSet;

Candidate.IgnoreObjectArgument =
cast<CXXMethodDecl>(Candidate.Function)->isStatic() ||
ObjectType.isNull();
Method->isStatic() ||
(!Method->isExplicitObjectMemberFunction() && ObjectType.isNull());
Candidate.ExplicitCallArguments = Args.size();
if (Result == TemplateDeductionResult::NonDependentConversionFailure)
Candidate.FailureKind = ovl_fail_bad_conversion;
Expand Down Expand Up @@ -8024,9 +8036,16 @@ static void AddTemplateOverloadCandidateImmediately(
Candidate.IsADLCandidate = llvm::to_underlying(IsADLCandidate);
// Ignore the object argument if there is one, since we don't have an object
// type.
Candidate.TookAddressOfOverload =
CandidateSet.getKind() ==
OverloadCandidateSet::CSK_AddressOfOverloadSet;

Candidate.IgnoreObjectArgument =
isa<CXXMethodDecl>(Candidate.Function) &&
cast<CXXMethodDecl>(Candidate.Function)
->isImplicitObjectMemberFunction() &&
!isa<CXXConstructorDecl>(Candidate.Function);

Candidate.ExplicitCallArguments = Args.size();
if (Result == TemplateDeductionResult::NonDependentConversionFailure)
Candidate.FailureKind = ovl_fail_bad_conversion;
Expand Down Expand Up @@ -8093,9 +8112,12 @@ bool Sema::CheckNonDependentConversions(
// that is correct.
const bool AllowExplicit = false;

bool ForOverloadSetAddressResolution =
CandidateSet.getKind() == OverloadCandidateSet::CSK_AddressOfOverloadSet;
auto *FD = FunctionTemplate->getTemplatedDecl();
auto *Method = dyn_cast<CXXMethodDecl>(FD);
bool HasThisConversion = Method && !isa<CXXConstructorDecl>(Method);
bool HasThisConversion = !ForOverloadSetAddressResolution && Method &&
!isa<CXXConstructorDecl>(Method);
unsigned ThisConversions = HasThisConversion ? 1 : 0;

if (Conversions.empty())
Expand Down Expand Up @@ -8165,7 +8187,8 @@ bool Sema::CheckNonDependentConversions(
};

unsigned Offset =
Method && Method->hasCXXExplicitFunctionObjectParameter() ? 1 : 0;
HasThisConversion && Method->hasCXXExplicitFunctionObjectParameter() ? 1
: 0;

for (unsigned I = 0, N = std::min(ParamTypes.size() - Offset, Args.size());
I != N; ++I) {
Expand Down Expand Up @@ -10780,7 +10803,8 @@ bool clang::isBetterOverloadCandidate(
// any function G, and, symmetrically, ICS1(G) is neither
// better nor worse than ICS1(F).
unsigned StartArg = 0;
if (Cand1.IgnoreObjectArgument || Cand2.IgnoreObjectArgument)
if (!Cand1.TookAddressOfOverload &&
(Cand1.IgnoreObjectArgument || Cand2.IgnoreObjectArgument))
StartArg = 1;

auto IsIllFormedConversion = [&](const ImplicitConversionSequence &ICS) {
Expand Down Expand Up @@ -11797,7 +11821,8 @@ static void DiagnoseBadConversion(Sema &S, OverloadCandidate *Cand,
// non-constructor method. Note that 'I' corresponds the
// conversion-slot index.
bool isObjectArgument = false;
if (isa<CXXMethodDecl>(Fn) && !isa<CXXConstructorDecl>(Fn)) {
if (!TakingCandidateAddress && isa<CXXMethodDecl>(Fn) &&
!isa<CXXConstructorDecl>(Fn)) {
if (I == 0)
isObjectArgument = true;
else if (!Fn->hasCXXExplicitFunctionObjectParameter())
Expand Down Expand Up @@ -12296,7 +12321,7 @@ static void DiagnoseBadDeduction(Sema &S, NamedDecl *Found, Decl *Templated,
}
case TemplateDeductionResult::TooManyArguments:
case TemplateDeductionResult::TooFewArguments:
DiagnoseArityMismatch(S, Found, Templated, NumArgs);
DiagnoseArityMismatch(S, Found, Templated, NumArgs, TakingCandidateAddress);
return;

case TemplateDeductionResult::InstantiationDepth:
Expand Down Expand Up @@ -13073,8 +13098,10 @@ CompleteNonViableCandidate(Sema &S, OverloadCandidate *Cand,

// Attempt to fix the bad conversion.
unsigned ConvCount = Cand->Conversions.size();
for (unsigned ConvIdx = (Cand->IgnoreObjectArgument ? 1 : 0); /**/;
++ConvIdx) {
for (unsigned ConvIdx =
((!Cand->TookAddressOfOverload && Cand->IgnoreObjectArgument) ? 1
: 0);
/**/; ++ConvIdx) {
assert(ConvIdx != ConvCount && "no bad conversion in candidate");
if (Cand->Conversions[ConvIdx].isInitialized() &&
Cand->Conversions[ConvIdx].isBad()) {
Expand Down Expand Up @@ -13259,7 +13286,7 @@ void OverloadCandidateSet::NoteCandidates(Sema &S, ArrayRef<Expr *> Args,

if (Cand->Function)
NoteFunctionCandidate(S, Cand, Args.size(),
/*TakingCandidateAddress=*/false, DestAS);
Kind == CSK_AddressOfOverloadSet, DestAS);
else if (Cand->IsSurrogate)
NoteSurrogateCandidate(S, Cand);
else {
Expand Down
11 changes: 8 additions & 3 deletions clang/lib/Sema/SemaTemplateDeduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3885,6 +3885,7 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
TemplateDeductionInfo &Info,
SmallVectorImpl<OriginalCallArg> const *OriginalCallArgs,
bool PartialOverloading, bool PartialOrdering,
bool ForOverloadSetAddressResolution,
llvm::function_ref<bool(bool)> CheckNonDependent) {
// Unevaluated SFINAE context.
EnterExpressionEvaluationContext Unevaluated(
Expand Down Expand Up @@ -4034,7 +4035,10 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(

auto ParamIdx = OriginalArg.ArgIdx;
unsigned ExplicitOffset =
Specialization->hasCXXExplicitFunctionObjectParameter() ? 1 : 0;
(Specialization->hasCXXExplicitFunctionObjectParameter() &&
!ForOverloadSetAddressResolution)
? 1
: 0;
if (ParamIdx >= Specialization->getNumParams() - ExplicitOffset)
// FIXME: This presumably means a pack ended up smaller than we
// expected while deducing. Should this not result in deduction
Expand Down Expand Up @@ -4681,6 +4685,7 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
Result = FinishTemplateArgumentDeduction(
FunctionTemplate, Deduced, NumExplicitlySpecified, Specialization, Info,
&OriginalCallArgs, PartialOverloading, PartialOrdering,
ForOverloadSetAddressResolution,
[&, CallingCtx](bool OnlyInitializeNonUserDefinedConversions) {
ContextRAII SavedContext(*this, CallingCtx);
return CheckNonDependent(ParamTypesForArgChecking,
Expand Down Expand Up @@ -4797,7 +4802,7 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
Result = FinishTemplateArgumentDeduction(
FunctionTemplate, Deduced, NumExplicitlySpecified, Specialization, Info,
/*OriginalCallArgs=*/nullptr, /*PartialOverloading=*/false,
/*PartialOrdering=*/true);
/*PartialOrdering=*/true, IsAddressOfFunction);
});
if (Result != TemplateDeductionResult::Success)
return Result;
Expand Down Expand Up @@ -4981,7 +4986,7 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
Result = FinishTemplateArgumentDeduction(
ConversionTemplate, Deduced, 0, ConversionSpecialized, Info,
&OriginalCallArgs, /*PartialOverloading=*/false,
/*PartialOrdering=*/false);
/*PartialOrdering=*/false, /*ForOverloadSetAddressResolution*/ false);
});
Specialization = cast_or_null<CXXConversionDecl>(ConversionSpecialized);
return Result;
Expand Down
25 changes: 20 additions & 5 deletions clang/test/CXX/drs/cwg26xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,14 +345,19 @@ void test() {
namespace cwg2692 { // cwg2692: 19
#if __cplusplus >= 202302L

struct A {
struct A {
static void f(A); // #cwg2692-1
void f(this A); // #cwg2692-2

void g();
};
template <typename T>
static void g(T); // #cwg2692-3
template <typename T>
void g(this T); // #cwg2692-4

void test();
};

void A::g() {
void A::test() {
(&A::f)(A());
// since-cxx23-error@-1 {{call to 'f' is ambiguous}}
// since-cxx23-note@#cwg2692-1 {{candidate function}}
Expand All @@ -361,6 +366,16 @@ namespace cwg2692 { // cwg2692: 19
// since-cxx23-error@-1 {{no matching function for call to 'f'}}
// since-cxx23-note@#cwg2692-1 {{candidate function not viable: requires 1 argument, but 0 were provided}}
// since-cxx23-note@#cwg2692-2 {{candidate function not viable: requires 1 argument, but 0 were provided}}
}


(&A::g)(A());
// since-cxx23-error@-1 {{call to 'g' is ambiguous}}
// since-cxx23-note@#cwg2692-3 {{candidate function}}
// since-cxx23-note@#cwg2692-4 {{candidate function}}
(&A::g<A>)();
// since-cxx23-error@-1 {{no matching function for call to 'g'}}
// since-cxx23-note@#cwg2692-3 {{candidate function template not viable: requires 1 argument, but 0 were provided}}
// since-cxx23-note@#cwg2692-4 {{candidate function [with T = cwg2692::A] not viable: requires 1 argument, but 0 were provided}}
}
#endif
} // namespace cwg2692
Loading