https://github.com/yxsamliu updated https://github.com/llvm/llvm-project/pull/208062
>From a2869657a397cb445dec3d593dbe3a81f086a134 Mon Sep 17 00:00:00 2001 From: "Yaxun (Sam) Liu" <[email protected]> Date: Tue, 7 Jul 2026 13:58:36 -0400 Subject: [PATCH 1/2] [Clang] Treat address-of template substitution as SFINAE Taking the address of a function template specialization can instantiate its trailing return type as part of a SFINAE probe: struct X {}; template <class T> void f(T) = delete; template <class T> auto probe(T t) -> decltype(f(t), void()); template <class T, class = decltype(&probe<T>)> int test(int); template <class T> char test(...); static_assert(sizeof(test<X>(0)) == sizeof(char)); Here, substituting the return type of probe<X> selects the deleted f<X>. The deleted call is in the immediate context of substitution, so it should remove test(int) from overload resolution. GCC accepts this example. Clang instead kept deduction as successful and later replayed the stored diagnostic as a hard error. The call-deduction path already checks the SFINAE trap after finishing template argument deduction. Do the same for the address-of path, so the stored diagnostic is not replayed after deduction succeeds. --- clang/docs/ReleaseNotes.md | 4 ++ clang/lib/Sema/SemaTemplateDeduction.cpp | 9 ++++ .../test/SemaTemplate/overload-candidates.cpp | 43 +++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 96317a3eece97..521f7df27b42a 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -843,6 +843,10 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the - Fixed a preprocessor assertion failure triggered when parsing an invalid template-id starting with `::template operator`. (#GH186582) - Fixed a crash when a function template is defined as a non-template friend with a global scope qualifier. (#GH185341) - Fixed a bug of incorrect template depth for abbreviated templates. (#GH200682) +- Fixed valid C++ code that uses an address-of-function-template expression, + such as `decltype(&T::func<Args...>)`, in SFINAE checks when substituting the + function template's trailing return type fails, such as when the return type + contains a `decltype` probe that calls a deleted function. - Clang now rejects constant template parameters with block pointer types, since these are not implemented anyway and would lead to crashes. (#GH189247) - Fixed some concept bugs introduced in Clang 22 (#GH197597) - Clang no longer reject call expressions whose type is a not-yet-deduced auto type. (#GH207565) diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp index d93b528facbcc..750ce54f3cd92 100644 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/lib/Sema/SemaTemplateDeduction.cpp @@ -4833,6 +4833,10 @@ TemplateDeductionResult Sema::DeduceTemplateArguments( /*HasDeducedAnyParam=*/nullptr); Result != TemplateDeductionResult::Success) return Result; + // Substituting the function type can instantiate the trailing return type, + // so handle the same immediate-context substitution failure here. + if (Trap.hasErrorOccurred()) + return TemplateDeductionResult::SubstitutionFailure; } TemplateDeductionResult Result; @@ -4842,6 +4846,11 @@ TemplateDeductionResult Sema::DeduceTemplateArguments( /*OriginalCallArgs=*/nullptr, /*PartialOverloading=*/false, /*PartialOrdering=*/true, IsAddressOfFunction); }); + if (Trap.hasErrorOccurred()) { + if (Specialization) + Specialization->setInvalidDecl(true); + return TemplateDeductionResult::SubstitutionFailure; + } if (Result != TemplateDeductionResult::Success) return Result; diff --git a/clang/test/SemaTemplate/overload-candidates.cpp b/clang/test/SemaTemplate/overload-candidates.cpp index 3a5bedca938bd..702cdb450de15 100644 --- a/clang/test/SemaTemplate/overload-candidates.cpp +++ b/clang/test/SemaTemplate/overload-candidates.cpp @@ -164,3 +164,46 @@ namespace PR15673 { void rangesv3(); // expected-note{{candidate template ignored: requirement 'PR15673::some_trait<int>::value' was not satisfied [with T = int, x = 42]}} void test_rangesv3() { rangesv3<int>(); } // expected-error{{no matching function for call to 'rangesv3'}} } + +#if __cplusplus >= 201103L +namespace AddressOfTemplateSFINAE { +template <class...> struct tag {}; + +struct Foo {}; + +template <class T> struct UniqueIf { + using Single = int; +}; +template <class T, unsigned N> struct UniqueIf<T[N]> { + using Bounded = int; +}; + +template <class T> typename UniqueIf<T>::Single make_unique() { return {}; } +template <class T, class... Args> +typename UniqueIf<T>::Bounded make_unique(Args &&...) = delete; + +struct Detector { + using Self = Detector; + + template <class, class T, class... Args> + static auto well_formed(Args &&...args) + -> decltype(void(), make_unique<T>(static_cast<Args &&>(args)...), + void()) {} + + template <class... Args, class = decltype(&Self::well_formed<Args...>)> + static constexpr bool test(tag<Args...> *, int) { + return true; + } + + static constexpr bool test(void *, long) { return false; } +}; + +template <class T, class... Args> struct has_make_unique { + static constexpr bool value = + Detector::test(static_cast<tag<void, T, Args...> *>(nullptr), 0); +}; + +static_assert(has_make_unique<int>::value, ""); +static_assert(!has_make_unique<Foo[2], int, int>::value, ""); +} // namespace AddressOfTemplateSFINAE +#endif >From 98de57b34c7decce36de9ad8ef321543ebdfc96d Mon Sep 17 00:00:00 2001 From: "Yaxun (Sam) Liu" <[email protected]> Date: Thu, 9 Jul 2026 14:21:31 -0400 Subject: [PATCH 2/2] [Clang] Explain address-of template SFINAE --- clang/lib/Sema/SemaTemplateDeduction.cpp | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp index 750ce54f3cd92..dc14a3b44ff13 100644 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/lib/Sema/SemaTemplateDeduction.cpp @@ -4846,6 +4846,37 @@ TemplateDeductionResult Sema::DeduceTemplateArguments( /*OriginalCallArgs=*/nullptr, /*PartialOverloading=*/false, /*PartialOrdering=*/true, IsAddressOfFunction); }); + // Taking the address of a function template forms its function type, and + // substituting into that type can require instantiating a trailing return + // type whose expression selects a deleted function. That is a deduction + // failure, not a hard error: + // + // C++ [temp.deduct.funcaddr]p1: + // [...] If there is a target, the function template's function type and + // the target type are used as the types of P and A, and the deduction is + // done as described in [temp.deduct.type]. + // + // C++ [temp.deduct.general]p7: + // [...] The substitution occurs in all types and expressions that are + // used in the deduction substitution loci. The expressions include [...] + // general expressions (i.e., non-constant expressions) inside sizeof, + // decltype, and other contexts that allow non-constant expressions. [...] + // + // C++ [dcl.fct.def.delete]p2: + // A construct that designates a deleted function implicitly or + // explicitly, other than to declare it [...], is ill-formed. + // [Note: [...] It applies even for references in expressions that are not + // potentially evaluated. - end note] + // + // C++ [temp.deduct.general]p8: + // If a substitution results in an invalid type or expression, type + // deduction fails. [...] Invalid types and expressions can result in a + // deduction failure only in the immediate context of the deduction + // substitution loci. [...] + // + // This substitution is in that immediate context, so treat diagnostics + // recorded by the SFINAE trap as deduction failure instead of replaying + // them as hard errors. if (Trap.hasErrorOccurred()) { if (Specialization) Specialization->setInvalidDecl(true); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
