Skip to content

Commit 34ae226

Browse files
authored
[Clang][Sema] Improve support for explicit specializations of constrained member functions & member function templates (llvm#88963)
Consider the following snippet from the discussion of CWG2847 on the core reflector: ``` template<typename T> concept C = sizeof(T) <= sizeof(long); template<typename T> struct A { template<typename U> void f(U) requires C<U>; // #1, declares a function template void g() requires C<T>; // rust-lang#2, declares a function template<> void f(char); // rust-lang#3, an explicit specialization of a function template that declares a function }; template<> template<typename U> void A<short>::f(U) requires C<U>; // rust-lang#4, an explicit specialization of a function template that declares a function template template<> template<> void A<int>::f(int); // rust-lang#5, an explicit specialization of a function template that declares a function template<> void A<long>::g(); // rust-lang#6, an explicit specialization of a function that declares a function ``` A number of problems exist: - Clang rejects `rust-lang#4` because the trailing _requires-clause_ has `U` substituted with the wrong template parameter depth when `Sema::AreConstraintExpressionsEqual` is called to determine whether it matches the trailing _requires-clause_ of the implicitly instantiated function template. - Clang rejects `rust-lang#5` because the function template specialization instantiated from `A<int>::f` has a trailing _requires-clause_, but `rust-lang#5` does not (nor can it have one as it isn't a templated function). - Clang rejects `rust-lang#6` for the same reasons it rejects `rust-lang#5`. This patch resolves these issues by making the following changes: - To fix `rust-lang#4`, `Sema::AreConstraintExpressionsEqual` is passed `FunctionTemplateDecl`s when comparing the trailing _requires-clauses_ of `rust-lang#4` and the function template instantiated from `#1`. - To fix `rust-lang#5` and `rust-lang#6`, the trailing _requires-clauses_ are not compared for explicit specializations that declare functions. In addition to these changes, `CheckMemberSpecialization` now considers constraint satisfaction/constraint partial ordering when determining which member function is specialized by an explicit specialization of a member function for an implicit instantiation of a class template (we previously would select the first function that has the same type as the explicit specialization). With constraints taken under consideration, we match EDG's behavior for these declarations.
1 parent 83f3b1c commit 34ae226

File tree

10 files changed

+248
-68
lines changed

10 files changed

+248
-68
lines changed

clang/docs/ReleaseNotes.rst

+4
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,10 @@ Bug Fixes to C++ Support
695695
until the noexcept-specifier is instantiated.
696696
- Fix a crash when an implicitly declared ``operator==`` function with a trailing requires-clause has its
697697
constraints compared to that of another declaration.
698+
- Fix a bug where explicit specializations of member functions/function templates would have substitution
699+
performed incorrectly when checking constraints. Fixes (#GH90349).
700+
- Clang now allows constrained member functions to be explicitly specialized for an implicit instantiation
701+
of a class template.
698702

699703
Bug Fixes to AST Handling
700704
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/Basic/DiagnosticSemaKinds.td

+5
Original file line numberDiff line numberDiff line change
@@ -5437,6 +5437,11 @@ def note_function_template_spec_matched : Note<
54375437
def err_function_template_partial_spec : Error<
54385438
"function template partial specialization is not allowed">;
54395439

5440+
def err_function_member_spec_ambiguous : Error<
5441+
"ambiguous member function specialization %q0 of %q1">;
5442+
def note_function_member_spec_matched : Note<
5443+
"member function specialization matches %0">;
5444+
54405445
// C++ Template Instantiation
54415446
def err_template_recursion_depth_exceeded : Error<
54425447
"recursive template instantiation exceeded maximum depth of %0">,

clang/include/clang/Sema/Sema.h

+3
Original file line numberDiff line numberDiff line change
@@ -9739,6 +9739,9 @@ class Sema final : public SemaBase {
97399739
const PartialDiagnostic &CandidateDiag,
97409740
bool Complain = true, QualType TargetType = QualType());
97419741

9742+
FunctionDecl *getMoreConstrainedFunction(FunctionDecl *FD1,
9743+
FunctionDecl *FD2);
9744+
97429745
///@}
97439746

97449747
//

clang/lib/Sema/SemaConcept.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ static const Expr *SubstituteConstraintExpressionWithoutSatisfaction(
811811
// this may happen while we're comparing two templates' constraint
812812
// equivalence.
813813
LocalInstantiationScope ScopeForParameters(S);
814-
if (auto *FD = llvm::dyn_cast<FunctionDecl>(DeclInfo.getDecl()))
814+
if (auto *FD = DeclInfo.getDecl()->getAsFunction())
815815
for (auto *PVD : FD->parameters())
816816
ScopeForParameters.InstantiatedLocal(PVD, PVD);
817817

clang/lib/Sema/SemaOverload.cpp

+19-53
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,8 @@ static bool IsOverloadOrOverrideImpl(Sema &SemaRef, FunctionDecl *New,
13031303
if (New->isMSVCRTEntryPoint())
13041304
return false;
13051305

1306+
NamedDecl *OldDecl = Old;
1307+
NamedDecl *NewDecl = New;
13061308
FunctionTemplateDecl *OldTemplate = Old->getDescribedFunctionTemplate();
13071309
FunctionTemplateDecl *NewTemplate = New->getDescribedFunctionTemplate();
13081310

@@ -1347,6 +1349,8 @@ static bool IsOverloadOrOverrideImpl(Sema &SemaRef, FunctionDecl *New,
13471349
// references to non-instantiated entities during constraint substitution.
13481350
// GH78101.
13491351
if (NewTemplate) {
1352+
OldDecl = OldTemplate;
1353+
NewDecl = NewTemplate;
13501354
// C++ [temp.over.link]p4:
13511355
// The signature of a function template consists of its function
13521356
// signature, its return type and its template parameter list. The names
@@ -1506,13 +1510,14 @@ static bool IsOverloadOrOverrideImpl(Sema &SemaRef, FunctionDecl *New,
15061510
}
15071511
}
15081512

1509-
if (!UseOverrideRules) {
1513+
if (!UseOverrideRules &&
1514+
New->getTemplateSpecializationKind() != TSK_ExplicitSpecialization) {
15101515
Expr *NewRC = New->getTrailingRequiresClause(),
15111516
*OldRC = Old->getTrailingRequiresClause();
15121517
if ((NewRC != nullptr) != (OldRC != nullptr))
15131518
return true;
1514-
1515-
if (NewRC && !SemaRef.AreConstraintExpressionsEqual(Old, OldRC, New, NewRC))
1519+
if (NewRC &&
1520+
!SemaRef.AreConstraintExpressionsEqual(OldDecl, OldRC, NewDecl, NewRC))
15161521
return true;
15171522
}
15181523

@@ -10695,29 +10700,10 @@ bool clang::isBetterOverloadCandidate(
1069510700
// -— F1 and F2 are non-template functions with the same
1069610701
// parameter-type-lists, and F1 is more constrained than F2 [...],
1069710702
if (!Cand1IsSpecialization && !Cand2IsSpecialization &&
10698-
sameFunctionParameterTypeLists(S, Cand1, Cand2)) {
10699-
FunctionDecl *Function1 = Cand1.Function;
10700-
FunctionDecl *Function2 = Cand2.Function;
10701-
if (FunctionDecl *MF = Function1->getInstantiatedFromMemberFunction())
10702-
Function1 = MF;
10703-
if (FunctionDecl *MF = Function2->getInstantiatedFromMemberFunction())
10704-
Function2 = MF;
10705-
10706-
const Expr *RC1 = Function1->getTrailingRequiresClause();
10707-
const Expr *RC2 = Function2->getTrailingRequiresClause();
10708-
if (RC1 && RC2) {
10709-
bool AtLeastAsConstrained1, AtLeastAsConstrained2;
10710-
if (S.IsAtLeastAsConstrained(Function1, RC1, Function2, RC2,
10711-
AtLeastAsConstrained1) ||
10712-
S.IsAtLeastAsConstrained(Function2, RC2, Function1, RC1,
10713-
AtLeastAsConstrained2))
10714-
return false;
10715-
if (AtLeastAsConstrained1 != AtLeastAsConstrained2)
10716-
return AtLeastAsConstrained1;
10717-
} else if (RC1 || RC2) {
10718-
return RC1 != nullptr;
10719-
}
10720-
}
10703+
sameFunctionParameterTypeLists(S, Cand1, Cand2) &&
10704+
S.getMoreConstrainedFunction(Cand1.Function, Cand2.Function) ==
10705+
Cand1.Function)
10706+
return true;
1072110707

1072210708
// -- F1 is a constructor for a class D, F2 is a constructor for a base
1072310709
// class B of D, and for all arguments the corresponding parameters of
@@ -13385,25 +13371,6 @@ Sema::resolveAddressOfSingleOverloadCandidate(Expr *E, DeclAccessPair &Pair) {
1338513371
static_cast<int>(CUDA().IdentifyPreference(Caller, FD2));
1338613372
};
1338713373

13388-
auto CheckMoreConstrained = [&](FunctionDecl *FD1,
13389-
FunctionDecl *FD2) -> std::optional<bool> {
13390-
if (FunctionDecl *MF = FD1->getInstantiatedFromMemberFunction())
13391-
FD1 = MF;
13392-
if (FunctionDecl *MF = FD2->getInstantiatedFromMemberFunction())
13393-
FD2 = MF;
13394-
SmallVector<const Expr *, 1> AC1, AC2;
13395-
FD1->getAssociatedConstraints(AC1);
13396-
FD2->getAssociatedConstraints(AC2);
13397-
bool AtLeastAsConstrained1, AtLeastAsConstrained2;
13398-
if (IsAtLeastAsConstrained(FD1, AC1, FD2, AC2, AtLeastAsConstrained1))
13399-
return std::nullopt;
13400-
if (IsAtLeastAsConstrained(FD2, AC2, FD1, AC1, AtLeastAsConstrained2))
13401-
return std::nullopt;
13402-
if (AtLeastAsConstrained1 == AtLeastAsConstrained2)
13403-
return std::nullopt;
13404-
return AtLeastAsConstrained1;
13405-
};
13406-
1340713374
// Don't use the AddressOfResolver because we're specifically looking for
1340813375
// cases where we have one overload candidate that lacks
1340913376
// enable_if/pass_object_size/...
@@ -13440,15 +13407,14 @@ Sema::resolveAddressOfSingleOverloadCandidate(Expr *E, DeclAccessPair &Pair) {
1344013407
}
1344113408
// FD has the same CUDA prefernece than Result. Continue check
1344213409
// constraints.
13443-
std::optional<bool> MoreConstrainedThanPrevious =
13444-
CheckMoreConstrained(FD, Result);
13445-
if (!MoreConstrainedThanPrevious) {
13446-
IsResultAmbiguous = true;
13447-
AmbiguousDecls.push_back(FD);
13410+
FunctionDecl *MoreConstrained = getMoreConstrainedFunction(FD, Result);
13411+
if (MoreConstrained != FD) {
13412+
if (!MoreConstrained) {
13413+
IsResultAmbiguous = true;
13414+
AmbiguousDecls.push_back(FD);
13415+
}
1344813416
continue;
1344913417
}
13450-
if (!*MoreConstrainedThanPrevious)
13451-
continue;
1345213418
// FD is more constrained - replace Result with it.
1345313419
}
1345413420
FoundBetter();
@@ -13467,7 +13433,7 @@ Sema::resolveAddressOfSingleOverloadCandidate(Expr *E, DeclAccessPair &Pair) {
1346713433
// constraints.
1346813434
if (getLangOpts().CUDA && CheckCUDAPreference(Skipped, Result) != 0)
1346913435
continue;
13470-
if (!CheckMoreConstrained(Skipped, Result))
13436+
if (!getMoreConstrainedFunction(Skipped, Result))
1347113437
return nullptr;
1347213438
}
1347313439
Pair = DAP;

clang/lib/Sema/SemaTemplate.cpp

+43-14
Original file line numberDiff line numberDiff line change
@@ -10339,24 +10339,53 @@ Sema::CheckMemberSpecialization(NamedDecl *Member, LookupResult &Previous) {
1033910339
if (Previous.empty()) {
1034010340
// Nowhere to look anyway.
1034110341
} else if (FunctionDecl *Function = dyn_cast<FunctionDecl>(Member)) {
10342+
SmallVector<FunctionDecl *> Candidates;
10343+
bool Ambiguous = false;
1034210344
for (LookupResult::iterator I = Previous.begin(), E = Previous.end();
1034310345
I != E; ++I) {
10344-
NamedDecl *D = (*I)->getUnderlyingDecl();
10345-
if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(D)) {
10346-
QualType Adjusted = Function->getType();
10347-
if (!hasExplicitCallingConv(Adjusted))
10348-
Adjusted = adjustCCAndNoReturn(Adjusted, Method->getType());
10349-
// This doesn't handle deduced return types, but both function
10350-
// declarations should be undeduced at this point.
10351-
if (Context.hasSameType(Adjusted, Method->getType())) {
10352-
FoundInstantiation = *I;
10353-
Instantiation = Method;
10354-
InstantiatedFrom = Method->getInstantiatedFromMemberFunction();
10355-
MSInfo = Method->getMemberSpecializationInfo();
10356-
break;
10357-
}
10346+
CXXMethodDecl *Method =
10347+
dyn_cast<CXXMethodDecl>((*I)->getUnderlyingDecl());
10348+
if (!Method)
10349+
continue;
10350+
QualType Adjusted = Function->getType();
10351+
if (!hasExplicitCallingConv(Adjusted))
10352+
Adjusted = adjustCCAndNoReturn(Adjusted, Method->getType());
10353+
// This doesn't handle deduced return types, but both function
10354+
// declarations should be undeduced at this point.
10355+
if (!Context.hasSameType(Adjusted, Method->getType()))
10356+
continue;
10357+
if (ConstraintSatisfaction Satisfaction;
10358+
Method->getTrailingRequiresClause() &&
10359+
(CheckFunctionConstraints(Method, Satisfaction,
10360+
/*UsageLoc=*/Member->getLocation(),
10361+
/*ForOverloadResolution=*/true) ||
10362+
!Satisfaction.IsSatisfied))
10363+
continue;
10364+
Candidates.push_back(Method);
10365+
FunctionDecl *MoreConstrained =
10366+
Instantiation ? getMoreConstrainedFunction(
10367+
Method, cast<FunctionDecl>(Instantiation))
10368+
: Method;
10369+
if (!MoreConstrained) {
10370+
Ambiguous = true;
10371+
continue;
10372+
}
10373+
if (MoreConstrained == Method) {
10374+
Ambiguous = false;
10375+
FoundInstantiation = *I;
10376+
Instantiation = Method;
10377+
InstantiatedFrom = Method->getInstantiatedFromMemberFunction();
10378+
MSInfo = Method->getMemberSpecializationInfo();
1035810379
}
1035910380
}
10381+
if (Ambiguous) {
10382+
Diag(Member->getLocation(), diag::err_function_member_spec_ambiguous)
10383+
<< Member << (InstantiatedFrom ? InstantiatedFrom : Instantiation);
10384+
for (FunctionDecl *Candidate : Candidates)
10385+
Diag(Candidate->getLocation(), diag::note_function_member_spec_matched)
10386+
<< Candidate;
10387+
return true;
10388+
}
1036010389
} else if (isa<VarDecl>(Member)) {
1036110390
VarDecl *PrevVar;
1036210391
if (Previous.isSingleResult() &&

clang/lib/Sema/SemaTemplateDeduction.cpp

+32
Original file line numberDiff line numberDiff line change
@@ -5852,6 +5852,38 @@ UnresolvedSetIterator Sema::getMostSpecialized(
58525852
return SpecEnd;
58535853
}
58545854

5855+
/// Returns the more constrained function according to the rules of
5856+
/// partial ordering by constraints (C++ [temp.constr.order]).
5857+
///
5858+
/// \param FD1 the first function
5859+
///
5860+
/// \param FD2 the second function
5861+
///
5862+
/// \returns the more constrained function. If neither function is
5863+
/// more constrained, returns NULL.
5864+
FunctionDecl *Sema::getMoreConstrainedFunction(FunctionDecl *FD1,
5865+
FunctionDecl *FD2) {
5866+
assert(!FD1->getDescribedTemplate() && !FD2->getDescribedTemplate() &&
5867+
"not for function templates");
5868+
FunctionDecl *F1 = FD1;
5869+
if (FunctionDecl *MF = FD1->getInstantiatedFromMemberFunction())
5870+
F1 = MF;
5871+
FunctionDecl *F2 = FD2;
5872+
if (FunctionDecl *MF = FD2->getInstantiatedFromMemberFunction())
5873+
F2 = MF;
5874+
llvm::SmallVector<const Expr *, 1> AC1, AC2;
5875+
F1->getAssociatedConstraints(AC1);
5876+
F2->getAssociatedConstraints(AC2);
5877+
bool AtLeastAsConstrained1, AtLeastAsConstrained2;
5878+
if (IsAtLeastAsConstrained(F1, AC1, F2, AC2, AtLeastAsConstrained1))
5879+
return nullptr;
5880+
if (IsAtLeastAsConstrained(F2, AC2, F1, AC1, AtLeastAsConstrained2))
5881+
return nullptr;
5882+
if (AtLeastAsConstrained1 == AtLeastAsConstrained2)
5883+
return nullptr;
5884+
return AtLeastAsConstrained1 ? FD1 : FD2;
5885+
}
5886+
58555887
/// Determine whether one partial specialization, P1, is at least as
58565888
/// specialized than another, P2.
58575889
///

clang/lib/Sema/SemaTemplateInstantiate.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,13 @@ Response HandleFunction(Sema &SemaRef, const FunctionDecl *Function,
275275
TemplateArgs->asArray(),
276276
/*Final=*/false);
277277

278+
if (RelativeToPrimary &&
279+
(Function->getTemplateSpecializationKind() ==
280+
TSK_ExplicitSpecialization ||
281+
(Function->getFriendObjectKind() &&
282+
!Function->getPrimaryTemplate()->getFriendObjectKind())))
283+
return Response::UseNextDecl(Function);
284+
278285
// If this function was instantiated from a specialized member that is
279286
// a function template, we're done.
280287
assert(Function->getPrimaryTemplate() && "No function template?");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// RUN: %clang_cc1 -std=c++20 -verify %s
2+
3+
template<int I>
4+
concept C = I >= 4;
5+
6+
template<int I>
7+
concept D = I < 8;
8+
9+
template<int I>
10+
struct A {
11+
constexpr static int f() { return 0; }
12+
constexpr static int f() requires C<I> && D<I> { return 1; }
13+
constexpr static int f() requires C<I> { return 2; }
14+
15+
constexpr static int g() requires C<I> { return 0; } // #candidate-0
16+
constexpr static int g() requires D<I> { return 1; } // #candidate-1
17+
18+
constexpr static int h() requires C<I> { return 0; } // expected-note {{member declaration nearly matches}}
19+
};
20+
21+
template<>
22+
constexpr int A<2>::f() { return 3; }
23+
24+
template<>
25+
constexpr int A<4>::f() { return 4; }
26+
27+
template<>
28+
constexpr int A<8>::f() { return 5; }
29+
30+
static_assert(A<3>::f() == 0);
31+
static_assert(A<5>::f() == 1);
32+
static_assert(A<9>::f() == 2);
33+
static_assert(A<2>::f() == 3);
34+
static_assert(A<4>::f() == 4);
35+
static_assert(A<8>::f() == 5);
36+
37+
template<>
38+
constexpr int A<0>::g() { return 2; }
39+
40+
template<>
41+
constexpr int A<8>::g() { return 3; }
42+
43+
template<>
44+
constexpr int A<6>::g() { return 4; } // expected-error {{ambiguous member function specialization 'A<6>::g' of 'A::g'}}
45+
// expected-note@#candidate-0 {{member function specialization matches 'g'}}
46+
// expected-note@#candidate-1 {{member function specialization matches 'g'}}
47+
48+
static_assert(A<9>::g() == 0);
49+
static_assert(A<1>::g() == 1);
50+
static_assert(A<0>::g() == 2);
51+
static_assert(A<8>::g() == 3);
52+
53+
template<>
54+
constexpr int A<4>::h() { return 1; }
55+
56+
template<>
57+
constexpr int A<0>::h() { return 2; } // expected-error {{out-of-line definition of 'h' does not match any declaration in 'A<0>'}}
58+
59+
static_assert(A<5>::h() == 0);
60+
static_assert(A<4>::h() == 1);

0 commit comments

Comments
 (0)