[clang] [Clang] Implement CWG2918 'Consideration of constraints for address of overloaded function' (PR #127773)

Younan Zhang via cfe-commits cfe-commits at lists.llvm.org
Thu Feb 20 04:21:33 PST 2025


https://github.com/zyn0217 updated https://github.com/llvm/llvm-project/pull/127773

>From 2d9c248c70b6d24f277982a32f36e2ef1bde2829 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Wed, 19 Feb 2025 17:13:56 +0800
Subject: [PATCH 1/2] [Clang] Implement CWG2918 'Consideration of constraints
 for address of overloaded function'

---
 clang/docs/ReleaseNotes.rst              |   4 +
 clang/include/clang/Sema/Sema.h          |   3 +-
 clang/lib/Sema/SemaConcept.cpp           |   5 +-
 clang/lib/Sema/SemaOverload.cpp          | 110 ++++++++++++++++++-----
 clang/lib/Sema/SemaTemplateDeduction.cpp |   9 +-
 clang/test/CXX/drs/cwg29xx.cpp           |  84 +++++++++++++++++
 clang/www/cxx_dr_status.html             |   2 +-
 7 files changed, 187 insertions(+), 30 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index a91c764860ccd..b876b48c63717 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -88,6 +88,10 @@ Resolutions to C++ Defect Reports
   two releases. The improvements to template template parameter matching implemented
   in the previous release, as described in P3310 and P3579, made this flag unnecessary.
 
+- Implemented `CWG2918 Consideration of constraints for address of overloaded `
+  `function <https://cplusplus.github.io/CWG/issues/2918.html>`_
+
+
 C Language Changes
 ------------------
 
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index c55b964650323..3270e7afb3796 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -10466,7 +10466,8 @@ class Sema final : public SemaBase {
   /// returned.
   FunctionDecl *ResolveSingleFunctionTemplateSpecialization(
       OverloadExpr *ovl, bool Complain = false, DeclAccessPair *Found = nullptr,
-      TemplateSpecCandidateSet *FailedTSC = nullptr);
+      TemplateSpecCandidateSet *FailedTSC = nullptr,
+      bool ForTypeDeduction = false);
 
   // Resolve and fix an overloaded expression that can be resolved
   // because it identifies a single function template specialization.
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 8a77cbf8c9477..f8bc176c1d8b5 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -1773,6 +1773,7 @@ bool Sema::IsAtLeastAsConstrained(NamedDecl *D1,
                                   NamedDecl *D2,
                                   MutableArrayRef<const Expr *> AC2,
                                   bool &Result) {
+#ifndef NDEBUG
   if (const auto *FD1 = dyn_cast<FunctionDecl>(D1)) {
     auto IsExpectedEntity = [](const FunctionDecl *FD) {
       FunctionDecl::TemplatedKind Kind = FD->getTemplatedKind();
@@ -1780,13 +1781,11 @@ bool Sema::IsAtLeastAsConstrained(NamedDecl *D1,
              Kind == FunctionDecl::TK_FunctionTemplate;
     };
     const auto *FD2 = dyn_cast<FunctionDecl>(D2);
-    (void)IsExpectedEntity;
-    (void)FD1;
-    (void)FD2;
     assert(IsExpectedEntity(FD1) && FD2 && IsExpectedEntity(FD2) &&
            "use non-instantiated function declaration for constraints partial "
            "ordering");
   }
+#endif
 
   if (AC1.empty()) {
     Result = AC2.empty();
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 8d5b5ac190b5b..77fc78dd8ce99 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -10369,20 +10369,16 @@ static bool allowAmbiguity(ASTContext &Context, const FunctionDecl *F1,
 /// [over.match.best.general]p2.6
 /// F1 and F2 are non-template functions with the same
 /// non-object-parameter-type-lists, and F1 is more constrained than F2 [...]
-static bool sameFunctionParameterTypeLists(Sema &S,
-                                           const OverloadCandidate &Cand1,
-                                           const OverloadCandidate &Cand2) {
-  if (!Cand1.Function || !Cand2.Function)
-    return false;
-
-  FunctionDecl *Fn1 = Cand1.Function;
-  FunctionDecl *Fn2 = Cand2.Function;
-
+static bool sameFunctionParameterTypeLists(Sema &S, FunctionDecl *Fn1,
+                                           FunctionDecl *Fn2,
+                                           bool IsFn1Reversed,
+                                           bool IsFn2Reversed) {
+  assert(Fn1 && Fn2);
   if (Fn1->isVariadic() != Fn2->isVariadic())
     return false;
 
-  if (!S.FunctionNonObjectParamTypesAreEqual(
-          Fn1, Fn2, nullptr, Cand1.isReversed() ^ Cand2.isReversed()))
+  if (!S.FunctionNonObjectParamTypesAreEqual(Fn1, Fn2, nullptr,
+                                             IsFn1Reversed ^ IsFn2Reversed))
     return false;
 
   auto *Mem1 = dyn_cast<CXXMethodDecl>(Fn1);
@@ -10403,6 +10399,30 @@ static bool sameFunctionParameterTypeLists(Sema &S,
   return true;
 }
 
+static FunctionDecl *
+getMorePartialOrderingConstrained(Sema &S, FunctionDecl *Fn1, FunctionDecl *Fn2,
+                                  bool IsFn1Reversed, bool IsFn2Reversed) {
+  if (!Fn1 || !Fn2)
+    return nullptr;
+
+  // C++ [temp.constr.order]:
+  //   A non-template function F1 is more partial-ordering-constrained than a
+  //   non-template function F2 if:
+  bool Cand1IsSpecialization = Fn1->getPrimaryTemplate();
+  bool Cand2IsSpecialization = Fn2->getPrimaryTemplate();
+
+  if (Cand1IsSpecialization || Cand2IsSpecialization)
+    return nullptr;
+
+  // - they have the same non-object-parameter-type-lists, and [...]
+  if (!sameFunctionParameterTypeLists(S, Fn1, Fn2, IsFn1Reversed,
+                                      IsFn2Reversed))
+    return nullptr;
+
+  // - the declaration of F1 is more constrained than the declaration of F2.
+  return S.getMoreConstrainedFunction(Fn1, Fn2);
+}
+
 /// isBetterOverloadCandidate - Determines whether the first overload
 /// candidate is a better candidate than the second (C++ 13.3.3p1).
 bool clang::isBetterOverloadCandidate(
@@ -10649,12 +10669,12 @@ bool clang::isBetterOverloadCandidate(
     }
   }
 
-  //   -— F1 and F2 are non-template functions with the same
-  //      parameter-type-lists, and F1 is more constrained than F2 [...],
-  if (!Cand1IsSpecialization && !Cand2IsSpecialization &&
-      sameFunctionParameterTypeLists(S, Cand1, Cand2) &&
-      S.getMoreConstrainedFunction(Cand1.Function, Cand2.Function) ==
-          Cand1.Function)
+  //   -— F1 and F2 are non-template functions and F1 is more
+  //      partial-ordering-constrained than F2 [...],
+  if (FunctionDecl *F = getMorePartialOrderingConstrained(
+          S, Cand1.Function, Cand2.Function, Cand1.isReversed(),
+          Cand2.isReversed());
+      F && F == Cand1.Function)
     return true;
 
   //   -- F1 is a constructor for a class D, F2 is a constructor for a base
@@ -12999,9 +13019,10 @@ class AddressOfFunctionResolver {
       // C++ [over.over]p4:
       //   If more than one function is selected, [...]
       if (Matches.size() > 1 && !eliminiateSuboptimalOverloadCandidates()) {
-        if (FoundNonTemplateFunction)
+        if (FoundNonTemplateFunction) {
           EliminateAllTemplateMatches();
-        else
+          EliminateLessPartialOrderingConstrainedMatches();
+        } else
           EliminateAllExceptMostSpecializedTemplate();
       }
     }
@@ -13252,6 +13273,33 @@ class AddressOfFunctionResolver {
     }
   }
 
+  void EliminateLessPartialOrderingConstrainedMatches() {
+    // C++ [over.over]p5:
+    //   [...] Any given non-template function F0 is eliminated if the set
+    //   contains a second non-template function that is more
+    //   partial-ordering-constrained than F0. [...]
+    assert(Matches[0].second->getPrimaryTemplate() == nullptr &&
+           "Call EliminateAllTemplateMatches() first");
+    SmallVector<std::pair<DeclAccessPair, FunctionDecl *>, 4> Results;
+    Results.push_back(Matches[0]);
+    for (unsigned I = 1, N = Matches.size(); I < N; ++I) {
+      assert(Matches[I].second->getPrimaryTemplate() == nullptr);
+      FunctionDecl *F = getMorePartialOrderingConstrained(
+          S, Matches[I].second, Results[0].second,
+          /*IsFn1Reversed=*/false,
+          /*IsFn2Reversed=*/false);
+      if (!F) {
+        Results.push_back(Matches[I]);
+        continue;
+      }
+      if (F == Matches[I].second) {
+        Results.clear();
+        Results.push_back(Matches[I]);
+      }
+    }
+    std::swap(Matches, Results);
+  }
+
   void EliminateSuboptimalCudaMatches() {
     S.CUDA().EraseUnwantedMatches(S.getCurFunctionDecl(/*AllowLambda=*/true),
                                   Matches);
@@ -13426,7 +13474,15 @@ Sema::resolveAddressOfSingleOverloadCandidate(Expr *E, DeclAccessPair &Pair) {
       }
       // FD has the same CUDA prefernece than Result. Continue check
       // constraints.
-      FunctionDecl *MoreConstrained = getMoreConstrainedFunction(FD, Result);
+
+      // C++ [over.over]p5:
+      //    [...] Any given non-template function F0 is eliminated if the set
+      //    contains a second non-template function that is more
+      //    partial-ordering-constrained than F0 [...]
+      FunctionDecl *MoreConstrained =
+          getMorePartialOrderingConstrained(*this, FD, Result,
+                                            /*IsFn1Reversed=*/false,
+                                            /*IsFn2Reversed=*/false);
       if (MoreConstrained != FD) {
         if (!MoreConstrained) {
           IsResultAmbiguous = true;
@@ -13443,9 +13499,9 @@ Sema::resolveAddressOfSingleOverloadCandidate(Expr *E, DeclAccessPair &Pair) {
     return nullptr;
 
   if (Result) {
-    SmallVector<const Expr *, 1> ResultAC;
     // We skipped over some ambiguous declarations which might be ambiguous with
     // the selected result.
+    // FIXME: This for-loop is dead code. Remove it?
     for (FunctionDecl *Skipped : AmbiguousDecls) {
       // If skipped candidate has different CUDA preference than the result,
       // there is no ambiguity. Otherwise check whether they have different
@@ -13489,7 +13545,7 @@ bool Sema::resolveAndFixAddressOfSingleOverloadCandidate(
 
 FunctionDecl *Sema::ResolveSingleFunctionTemplateSpecialization(
     OverloadExpr *ovl, bool Complain, DeclAccessPair *FoundResult,
-    TemplateSpecCandidateSet *FailedTSC) {
+    TemplateSpecCandidateSet *FailedTSC, bool ForTypeDeduction) {
   // C++ [over.over]p1:
   //   [...] [Note: any redundant set of parentheses surrounding the
   //   overloaded function name is ignored (5.1). ]
@@ -13540,8 +13596,16 @@ FunctionDecl *Sema::ResolveSingleFunctionTemplateSpecialization(
 
     assert(Specialization && "no specialization and no error?");
 
-    // Multiple matches; we can't resolve to a single declaration.
+    // C++ [temp.deduct.call]p6:
+    //    [...] If all successful deductions yield the same deduced A, that
+    //    deduced A is the result of deduction; otherwise, the parameter is
+    //    treated as a non-deduced context.
     if (Matched) {
+      if (ForTypeDeduction &&
+          isSameOrCompatibleFunctionType(Matched->getType(),
+                                         Specialization->getType()))
+        continue;
+      // Multiple matches; we can't resolve to a single declaration.
       if (Complain) {
         Diag(ovl->getExprLoc(), diag::err_addr_ovl_ambiguous)
           << ovl->getName();
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index eb70dd743c8be..627cd82ed1c77 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4252,7 +4252,8 @@ ResolveOverloadForDeduction(Sema &S, TemplateParameterList *TemplateParams,
       if (FunctionDecl *ExplicitSpec =
               S.ResolveSingleFunctionTemplateSpecialization(
                   Ovl, /*Complain=*/false,
-                  /*FoundDeclAccessPair=*/nullptr, FailedTSC))
+                  /*Found=*/nullptr, FailedTSC,
+                  /*ForTypeDeduction=*/true))
         return GetTypeOfFunction(S, R, ExplicitSpec);
     }
 
@@ -4321,7 +4322,11 @@ ResolveOverloadForDeduction(Sema &S, TemplateParameterList *TemplateParams,
         /*HasDeducedAnyParam=*/nullptr);
     if (Result != TemplateDeductionResult::Success)
       continue;
-    if (!Match.isNull())
+    // C++ [temp.deduct.call]p6:
+    //   [...] If all successful deductions yield the same deduced A, that
+    //   deduced A is the result of deduction; otherwise, the parameter is
+    //   treated as a non-deduced context. [...]
+    if (!Match.isNull() && !S.isSameOrCompatibleFunctionType(Match, ArgType))
       return {};
     Match = ArgType;
   }
diff --git a/clang/test/CXX/drs/cwg29xx.cpp b/clang/test/CXX/drs/cwg29xx.cpp
index aeb62b8d68710..61c3ecc04b895 100644
--- a/clang/test/CXX/drs/cwg29xx.cpp
+++ b/clang/test/CXX/drs/cwg29xx.cpp
@@ -60,6 +60,90 @@ struct S {
 #endif
 } // namespace cwg2917
 
+namespace cwg2918 { // cwg2918: 21
+
+#if __cplusplus >= 202002L
+
+namespace Example1 {
+
+template<bool B> struct X {
+  void f(short) requires B;
+  void f(long);
+  template<typename> void g(short) requires B;
+  template<typename> void g(long);
+};
+
+void test() {
+  &X<true>::f;      // since-cxx20-error {{reference to overloaded function could not be resolved}}
+  &X<true>::g<int>; // since-cxx20-error {{reference to overloaded function could not be resolved}}
+}
+
+} // namespace Example1
+
+namespace Example2 {
+
+template <bool B> struct X {
+  static constexpr int f(short) requires B {
+    return 42;
+  }
+  static constexpr int f(short) {
+    return 24;
+  }
+};
+
+template <typename T>
+constexpr int f(T) { return 1; }
+
+template <typename T>
+  requires __is_same(T, int)
+constexpr int f(T) { return 2; }
+
+void test() {
+  constexpr auto x = &X<true>::f;
+  static_assert(__is_same(decltype(x), int(*const)(short)), "");
+  static_assert(x(0) == 42, "");
+
+  constexpr auto y = &X<false>::f;
+  static_assert(__is_same(decltype(x), int(*const)(short)));
+  static_assert(y(0) == 24, "");
+  
+  constexpr auto z = &f<int>;
+  static_assert(__is_same(decltype(z), int(*const)(int)));
+  static_assert(z(0) == 2, "");
+
+  // C++ [temp.deduct.call]p6:
+  //   If the argument is an overload set containing one or more function templates,
+  //   the parameter is treated as a non-deduced context.
+  auto w = f; // since-cxx20-error {{variable 'w' with type 'auto' has incompatible initializer of type '<overloaded function type>'}}
+}
+
+} // namespace Example2
+
+namespace Example3 {
+
+template <typename T> void f(T &&, void (*)(T &&)); // #cwg2918_f
+
+void g(int &); // #1
+inline namespace A {
+void g(short &&); // #2
+}
+inline namespace B {
+void g(short &&); // #3
+}
+
+void q() {
+  int x;
+  f(x, g);
+  // since-cxx20-error at -1 {{no matching function for call to 'f'}}
+  //   since-cxx20-note@#cwg2918_f {{candidate template ignored: deduced conflicting types for parameter 'T' ('int &' vs. 'short')}}
+}
+
+} // namespace Example3
+
+#endif
+
+} // namespace cwg2918
+
 #if __cplusplus > 202302L
 namespace std {
   using size_t = decltype(sizeof(0));
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 69ddd5e58b921..b674f8f094bb8 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -17364,7 +17364,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
     <td><a href="https://cplusplus.github.io/CWG/issues/2918.html">2918</a></td>
     <td>DR</td>
     <td>Consideration of constraints for address of overloaded function</td>
-    <td class="unknown" align="center">Unknown</td>
+    <td class="unreleased" align="center">Clang 21</td>
   </tr>
   <tr id="2919">
     <td><a href="https://cplusplus.github.io/CWG/issues/2919.html">2919</a></td>

>From 5388856d4f693fa8bb0df6c5d73ce9be86907ed8 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Thu, 20 Feb 2025 20:20:57 +0800
Subject: [PATCH 2/2] Test Example3 in C++11+

---
 clang/test/CXX/drs/cwg29xx.cpp | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/clang/test/CXX/drs/cwg29xx.cpp b/clang/test/CXX/drs/cwg29xx.cpp
index 61c3ecc04b895..12dc966193e89 100644
--- a/clang/test/CXX/drs/cwg29xx.cpp
+++ b/clang/test/CXX/drs/cwg29xx.cpp
@@ -104,7 +104,7 @@ void test() {
   static_assert(x(0) == 42, "");
 
   constexpr auto y = &X<false>::f;
-  static_assert(__is_same(decltype(x), int(*const)(short)));
+  static_assert(__is_same(decltype(y), int(*const)(short)));
   static_assert(y(0) == 24, "");
   
   constexpr auto z = &f<int>;
@@ -118,7 +118,9 @@ void test() {
 }
 
 } // namespace Example2
+#endif
 
+#if __cplusplus >= 201103L
 namespace Example3 {
 
 template <typename T> void f(T &&, void (*)(T &&)); // #cwg2918_f
@@ -134,8 +136,8 @@ void g(short &&); // #3
 void q() {
   int x;
   f(x, g);
-  // since-cxx20-error at -1 {{no matching function for call to 'f'}}
-  //   since-cxx20-note@#cwg2918_f {{candidate template ignored: deduced conflicting types for parameter 'T' ('int &' vs. 'short')}}
+  // since-cxx11-error at -1 {{no matching function for call to 'f'}}
+  //   since-cxx11-note@#cwg2918_f {{candidate template ignored: deduced conflicting types for parameter 'T' ('int &' vs. 'short')}}
 }
 
 } // namespace Example3



More information about the cfe-commits mailing list