[clang] [Clang][Sema] Improve support for explicit specializations of constrained member functions & member function templates (PR #88963)

via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 16 11:59:11 PDT 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Krystian Stasiowski (sdkrystian)

<details>
<summary>Changes</summary>

Consider the following snippet from the discussion of [CWG2847](https://cplusplus.github.io/CWG/issues/2847.html) on the [core reflector](https://lists.isocpp.org/core/2024/04/15720.php) ([godbolt link](https://godbolt.org/z/dvvef9xzM)):
```cpp
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>; // #<!-- -->2, declares a function

    template<>
    void f(char);  // #<!-- -->3, an explicit specialization of a function template that declares a function
};

template<>
template<typename U>
void A<short>::f(U) requires C<U>; // #<!-- -->4, an explicit specialization of a function template that declares a function template

template<>
template<>
void A<int>::f(int); // #<!-- -->5, an explicit specialization of a function template that declares a function

template<>
void A<long>::g(); // #<!-- -->6, an explicit specialization of a function that declares a function
```

A number of problems exist:
- Clang rejects `#<!-- -->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 `#<!-- -->5` because the function template specialization instantiated from `A<int>::f` has a trailing _requires-clause_, but `#<!-- -->5` does not (nor can it have one as it isn't a templated function).
- Clang rejects `#<!-- -->6` for the same reasons it rejects `#<!-- -->5`.

This patch resolves these issues by making the following changes:
- To fix `#<!-- -->4`, `Sema::AreConstraintExpressionsEqual` is passed `FunctionTemplateDecl`s when comparing the trailing _requires-clauses_ of `#<!-- -->4` and the function template instantiated from `#<!-- -->1`.
- To fix `#<!-- -->5` and `#<!-- -->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.

---
Full diff: https://github.com/llvm/llvm-project/pull/88963.diff


5 Files Affected:

- (modified) clang/lib/Sema/SemaConcept.cpp (+1-1) 
- (modified) clang/lib/Sema/SemaOverload.cpp (+8-3) 
- (modified) clang/lib/Sema/SemaTemplate.cpp (+37-7) 
- (modified) clang/lib/Sema/SemaTemplateInstantiate.cpp (+4) 
- (added) clang/test/CXX/temp/temp.spec/temp.expl.spec/p8.cpp (+74) 


``````````diff
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index e00c972602829e..7bfec4e11f7aab 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -811,7 +811,7 @@ static const Expr *SubstituteConstraintExpressionWithoutSatisfaction(
   // this may happen while we're comparing two templates' constraint
   // equivalence.
   LocalInstantiationScope ScopeForParameters(S);
-  if (auto *FD = llvm::dyn_cast<FunctionDecl>(DeclInfo.getDecl()))
+  if (auto *FD = DeclInfo.getDecl()->getAsFunction())
     for (auto *PVD : FD->parameters())
       ScopeForParameters.InstantiatedLocal(PVD, PVD);
 
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 227ef564ba3e08..594cfc58d2226a 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1303,6 +1303,8 @@ static bool IsOverloadOrOverrideImpl(Sema &SemaRef, FunctionDecl *New,
   if (New->isMSVCRTEntryPoint())
     return false;
 
+  NamedDecl *OldDecl = Old;
+  NamedDecl *NewDecl = New;
   FunctionTemplateDecl *OldTemplate = Old->getDescribedFunctionTemplate();
   FunctionTemplateDecl *NewTemplate = New->getDescribedFunctionTemplate();
 
@@ -1347,6 +1349,8 @@ static bool IsOverloadOrOverrideImpl(Sema &SemaRef, FunctionDecl *New,
   // references to non-instantiated entities during constraint substitution.
   // GH78101.
   if (NewTemplate) {
+    OldDecl = OldTemplate;
+    NewDecl = NewTemplate;
     // C++ [temp.over.link]p4:
     //   The signature of a function template consists of its function
     //   signature, its return type and its template parameter list. The names
@@ -1506,13 +1510,14 @@ static bool IsOverloadOrOverrideImpl(Sema &SemaRef, FunctionDecl *New,
     }
   }
 
-  if (!UseOverrideRules) {
+  if (!UseOverrideRules &&
+      New->getTemplateSpecializationKind() != TSK_ExplicitSpecialization) {
     Expr *NewRC = New->getTrailingRequiresClause(),
          *OldRC = Old->getTrailingRequiresClause();
     if ((NewRC != nullptr) != (OldRC != nullptr))
       return true;
-
-    if (NewRC && !SemaRef.AreConstraintExpressionsEqual(Old, OldRC, New, NewRC))
+    if (NewRC &&
+        !SemaRef.AreConstraintExpressionsEqual(OldDecl, OldRC, NewDecl, NewRC))
       return true;
   }
 
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 95171359f0ab17..3cb1ee1d5b795d 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -10338,6 +10338,25 @@ bool Sema::CheckFunctionTemplateSpecialization(
   return false;
 }
 
+static bool IsMoreConstrainedFunction(Sema &S, FunctionDecl *FD1,
+                                      FunctionDecl *FD2) {
+  if (FunctionDecl *MF = FD1->getInstantiatedFromMemberFunction())
+    FD1 = MF;
+  if (FunctionDecl *MF = FD2->getInstantiatedFromMemberFunction())
+    FD2 = MF;
+  llvm::SmallVector<const Expr *, 3> AC1, AC2;
+  FD1->getAssociatedConstraints(AC1);
+  FD2->getAssociatedConstraints(AC2);
+  bool AtLeastAsConstrained1, AtLeastAsConstrained2;
+  if (S.IsAtLeastAsConstrained(FD1, AC1, FD2, AC2, AtLeastAsConstrained1))
+    return false;
+  if (S.IsAtLeastAsConstrained(FD2, AC2, FD1, AC1, AtLeastAsConstrained2))
+    return false;
+  if (AtLeastAsConstrained1 == AtLeastAsConstrained2)
+    return false;
+  return AtLeastAsConstrained1;
+}
+
 /// Perform semantic analysis for the given non-template member
 /// specialization.
 ///
@@ -10372,15 +10391,26 @@ Sema::CheckMemberSpecialization(NamedDecl *Member, LookupResult &Previous) {
         QualType Adjusted = Function->getType();
         if (!hasExplicitCallingConv(Adjusted))
           Adjusted = adjustCCAndNoReturn(Adjusted, Method->getType());
+        if (!Context.hasSameType(Adjusted, Method->getType()))
+          continue;
+        if (Method->getTrailingRequiresClause()) {
+          ConstraintSatisfaction Satisfaction;
+          if (CheckFunctionConstraints(Method, Satisfaction,
+                                       /*UsageLoc=*/Member->getLocation(),
+                                       /*ForOverloadResolution=*/true) ||
+              !Satisfaction.IsSatisfied)
+            continue;
+          if (Instantiation &&
+              !IsMoreConstrainedFunction(*this, Method,
+                                         cast<CXXMethodDecl>(Instantiation)))
+            continue;
+        }
         // This doesn't handle deduced return types, but both function
         // declarations should be undeduced at this point.
-        if (Context.hasSameType(Adjusted, Method->getType())) {
-          FoundInstantiation = *I;
-          Instantiation = Method;
-          InstantiatedFrom = Method->getInstantiatedFromMemberFunction();
-          MSInfo = Method->getMemberSpecializationInfo();
-          break;
-        }
+        FoundInstantiation = *I;
+        Instantiation = Method;
+        InstantiatedFrom = Method->getInstantiatedFromMemberFunction();
+        MSInfo = Method->getMemberSpecializationInfo();
       }
     }
   } else if (isa<VarDecl>(Member)) {
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 7cd428de0bb32d..09a7f34d651db2 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -275,6 +275,10 @@ Response HandleFunction(Sema &SemaRef, const FunctionDecl *Function,
                                      TemplateArgs->asArray(),
                                      /*Final=*/false);
 
+    if (RelativeToPrimary &&
+        Function->getTemplateSpecializationKind() == TSK_ExplicitSpecialization)
+      return Response::UseNextDecl(Function);
+
     // If this function was instantiated from a specialized member that is
     // a function template, we're done.
     assert(Function->getPrimaryTemplate() && "No function template?");
diff --git a/clang/test/CXX/temp/temp.spec/temp.expl.spec/p8.cpp b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p8.cpp
new file mode 100644
index 00000000000000..87e10d10e4b453
--- /dev/null
+++ b/clang/test/CXX/temp/temp.spec/temp.expl.spec/p8.cpp
@@ -0,0 +1,74 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+template<typename T>
+concept C = sizeof(T) <= sizeof(long);
+
+template<typename T>
+struct A {
+  template<typename U>
+  void f(U) requires C<U>;
+
+  void g() requires C<T>;
+
+  template<typename U>
+  void h(U) requires C<T>;
+
+  constexpr int i() requires C<T> {
+    return 0;
+  }
+
+  constexpr int i() requires C<T> && true {
+    return 1;
+  }
+
+  template<>
+  void f(char);
+};
+
+template<>
+template<typename U>
+void A<short>::f(U) requires C<U>;
+
+template<>
+template<typename U>
+void A<short>::h(U) requires C<short>;
+
+template<>
+template<>
+void A<int>::f(int);
+
+template<>
+void A<long>::g();
+
+template<>
+constexpr int A<long>::i() {
+  return 2;
+}
+
+static_assert(A<long>().i() == 2);
+
+template<typename T>
+struct D {
+  template<typename U>
+  static constexpr int f(U);
+
+  template<typename U>
+  static constexpr int f(U) requires (sizeof(T) == 1);
+
+  template<>
+  constexpr int f(int) {
+    return 1;
+  }
+};
+
+template<>
+template<typename U>
+constexpr int D<signed char>::f(U) requires (sizeof(signed char) == 1) {
+  return 0;
+}
+
+static_assert(D<char>::f(0) == 1);
+static_assert(D<char[2]>::f(0) == 1);
+static_assert(D<signed char>::f(0) == 1);
+static_assert(D<signed char>::f(0.0) == 0);

``````````

</details>


https://github.com/llvm/llvm-project/pull/88963


More information about the cfe-commits mailing list