https://github.com/BaLiKfromUA updated https://github.com/llvm/llvm-project/pull/225239
>From 368757fd84d130c0ed250efa489cb56e8a3e2449 Mon Sep 17 00:00:00 2001 From: BaLiKfromUA <[email protected]> Date: Tue, 22 Sep 2026 01:01:23 +0100 Subject: [PATCH 1/4] [clang] Fix partial specialization matching for NTTPs of placeholder type --- clang/docs/ReleaseNotes.md | 7 + clang/lib/AST/ASTContext.cpp | 6 +- .../SemaTemplate/temp_arg_nontype_cxx1z.cpp | 162 ++++++++++++++++-- 3 files changed, 160 insertions(+), 15 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index a1f24a8caedae..63e698a84a53b 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -727,6 +727,13 @@ features cannot lower the translation-unit ABI level; - Fixed ambiguous overload where two non-static member functions with different signatures could be incorrectly considered equivalent. (#GH224499) +- Fixed template argument deduction incorrectly selecting a class or variable + template partial specialization whose non-type template argument has a + different type from the argument it is matched against, when the corresponding + parameter of the primary template has a placeholder type. For example, given + `template <class T, auto V> struct S`, the partial specialization `S<T, 0>` is + no longer selected for `S<void, 0L>`. (#GH124186), (#GH42421), (#GH53982) + #### Bug Fixes to AST Handling - Fixed a non-deterministic ordering of unused local typedefs that made diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index ee8663e6906af..926a8f7b7ee4b 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -8040,7 +8040,11 @@ bool ASTContext::isSameTemplateArgument(const TemplateArgument &Arg1, getCanonicalTemplateName(Arg2.getAsTemplateOrTemplatePattern()); case TemplateArgument::Integral: - return llvm::APSInt::isSameValue(Arg1.getAsIntegral(), + // The types have to match as well as the values: + // C++ [temp.type]p2 + // C++ [temp.deduct.type]p20 + return hasSameType(Arg1.getIntegralType(), Arg2.getIntegralType()) && + llvm::APSInt::isSameValue(Arg1.getAsIntegral(), Arg2.getAsIntegral()); case TemplateArgument::StructuralValue: diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp index a39bb02084aa7..1621983be64fa 100644 --- a/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp @@ -267,22 +267,23 @@ namespace Auto { } namespace Decomposition { - // Types of deduced non-type template arguments must match exactly, so - // partial ordering fails in both directions here. - template<auto> struct Any; - template<int N> struct Any<N> { typedef int Int; }; // expected-note 3{{match}} - template<short N> struct Any<N> { typedef int Short; }; // expected-note 3{{match}} - Any<0>::Int is_int; // expected-error {{ambiguous}} - Any<(short)0>::Short is_short; // expected-error {{ambiguous}} - Any<(char)0>::Short is_char; // expected-error {{ambiguous}} + // Types of deduced non-type template arguments must match exactly, so each + // of these selects at most one partial specialization: the one whose + // parameter has the same type as the argument. + template<auto> struct Any; // expected-note {{template is declared here}} + template<int N> struct Any<N> { typedef int Int; }; + template<short N> struct Any<N> { typedef int Short; }; + Any<0>::Int is_int; + Any<(short)0>::Short is_short; + Any<(char)0>::Short is_char; // expected-error {{implicit instantiation of undefined template 'Auto::Decomposition::Any<'\x00'>'}} template<int, auto> struct NestedAny; - template<auto N> struct NestedAny<0, N>; // expected-note 3{{match}} - template<int N> struct NestedAny<0, N> { typedef int Int; }; // expected-note 3{{match}} - template<short N> struct NestedAny<0, N> { typedef int Short; }; // expected-note 3{{match}} - NestedAny<0, 0>::Int nested_int; // expected-error {{ambiguous}} - NestedAny<0, (short)0>::Short nested_short; // expected-error {{ambiguous}} - NestedAny<0, (char)0>::Short nested_char; // expected-error {{ambiguous}} + template<auto N> struct NestedAny<0, N>; // expected-note {{template is declared here}} + template<int N> struct NestedAny<0, N> { typedef int Int; }; + template<short N> struct NestedAny<0, N> { typedef int Short; }; + NestedAny<0, 0>::Int nested_int; + NestedAny<0, (short)0>::Short nested_short; + NestedAny<0, (char)0>::Short nested_char; // expected-error {{implicit instantiation of undefined template 'Auto::Decomposition::NestedAny<0, '\x00'>'}} double foo(int, bool); template<auto& f> struct fn_result_type; @@ -649,6 +650,139 @@ namespace GH58682 { static_assert(B<A<(g)>>::k == 1, ""); } // namespace GH58682 +namespace GH124186 { + template <class T, auto V> struct S { + static constexpr int value = 0; + }; + + template <class T> struct S<T, 0> { + static constexpr int value = 1; + }; + + enum E { Zero }; + + static_assert(S<void, 0>::value == 1); + static_assert(S<void, 0L>::value == 0); + static_assert(S<void, 0U>::value == 0); + static_assert(S<void, false>::value == 0); + static_assert(S<void, Zero>::value == 0); + + // For a parameter of non-placeholder type the argument is converted to the + // type of the parameter, so these all still match. + template <class T, int V> struct F { + static constexpr int value = 0; + }; + + template <class T> struct F<T, 0> { + static constexpr int value = 1; + }; + + static_assert(F<void, 0>::value == 1); + static_assert(F<void, 0L>::value == 1); + static_assert(F<void, 0U>::value == 1); + + // Test with aliasing and cv modifiers + typedef int my_int; + using my_const_int = const int; + + template <class T, auto V> struct G { + static constexpr int value = 0; + }; + + template <class T> struct G<T, (my_int)0> { + static constexpr int value = 1; + }; + + static_assert(G<void, 0>::value == 1); + static_assert(G<void, 0L>::value == 0); + + template <auto V> struct H { + static constexpr int value = 0; + }; + + template <my_int V> struct H<V> { + static constexpr int value = 1; + }; + + template <auto V> struct I { + static constexpr int value = 0; + }; + + template <my_const_int V> struct I<V> { + static constexpr int value = 1; + }; + + static_assert(H<0>::value == 1); + static_assert(H<0L>::value == 0); + static_assert(I<0>::value == 1); + static_assert(I<0L>::value == 0); + + // Variable templates behave the same way. + template <class T, auto V> constexpr int value = 0; + template <class T> constexpr int value<T, 0> = 1; + + static_assert(value<void, 0> == 1); + static_assert(value<void, 0L> == 0); + static_assert(value<void, 0U> == 0); + static_assert(value<void, false> == 0); +} // namespace GH124186 + +namespace GH42421 { + template <auto V> struct S { + static constexpr int value = 0; + }; + + template <int I> struct S<I> { + static constexpr int value = 1; + }; + + static_assert(S<42>::value == 1); + // A long or unsigned argument does not match an int parameter. + static_assert(S<42L>::value == 0); + static_assert(S<42U>::value == 0); + + // Only the partial specialization is type-sensitive here; the explicit + // specialization is not a candidate for Q<42U, int> either way. + template <auto I, class T> struct Q { + static constexpr int value = 0; + }; + + template <class T> struct Q<42, T> { + static constexpr int value = 1; + }; + + template <> struct Q<42, int> { + static constexpr int value = 2; + }; + + static_assert(Q<42U, int>::value == 0); + // The explicit specialization still wins for an int argument. + static_assert(Q<42, int>::value == 2); +} // namespace GH42421 + +namespace GH53982 { + enum class E1 : unsigned int { E11 = 1 }; + enum class E2 : unsigned int { E21 = 1 }; + + template <int j, auto i> struct C { + static constexpr int value = 0; + }; + + template <int j> struct C<j, E1::E11> { + static constexpr int value = 1; + }; + + template <int j> struct C<j, E2::E21> { + static constexpr int value = 2; + }; + + static_assert(C<0, E1::E11>::value == 1); + static_assert(C<1, E2::E21>::value == 2); + + // The shared underlying value on its own matches neither. + static_assert(C<0, 1U>::value == 0); +} // namespace GH53982 + // C++26 [temp.deduct.type]p13, Example 8. namespace temp_deduct_type_p13 { template<long n> struct A { }; >From c39fe2aaef36210dfe226e5105f016e541e11024 Mon Sep 17 00:00:00 2001 From: Valentyn Yukhymenko <[email protected]> Date: Tue, 22 Sep 2026 01:37:28 +0100 Subject: [PATCH 2/4] Simplify error message matching --- clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp index 1621983be64fa..1472aaa632400 100644 --- a/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp @@ -275,7 +275,7 @@ namespace Auto { template<short N> struct Any<N> { typedef int Short; }; Any<0>::Int is_int; Any<(short)0>::Short is_short; - Any<(char)0>::Short is_char; // expected-error {{implicit instantiation of undefined template 'Auto::Decomposition::Any<'\x00'>'}} + Any<(char)0>::Short is_char; // expected-error {{implicit instantiation of undefined template}} template<int, auto> struct NestedAny; template<auto N> struct NestedAny<0, N>; // expected-note {{template is declared here}} @@ -283,7 +283,7 @@ namespace Auto { template<short N> struct NestedAny<0, N> { typedef int Short; }; NestedAny<0, 0>::Int nested_int; NestedAny<0, (short)0>::Short nested_short; - NestedAny<0, (char)0>::Short nested_char; // expected-error {{implicit instantiation of undefined template 'Auto::Decomposition::NestedAny<0, '\x00'>'}} + NestedAny<0, (char)0>::Short nested_char; // expected-error {{implicit instantiation of undefined template}} double foo(int, bool); template<auto& f> struct fn_result_type; >From ecba51ddc3bea0baa9b9ca4a1d62972456efc826 Mon Sep 17 00:00:00 2001 From: Valentyn Yukhymenko <[email protected]> Date: Tue, 22 Sep 2026 01:39:16 +0100 Subject: [PATCH 3/4] Remove second reference to temp.deduce --- clang/lib/AST/ASTContext.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index ef078a12ba164..799f2bfc48a80 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -8042,7 +8042,6 @@ bool ASTContext::isSameTemplateArgument(const TemplateArgument &Arg1, case TemplateArgument::Integral: // The types have to match as well as the values: // C++ [temp.type]p2 - // C++ [temp.deduct.type]p20 return hasSameType(Arg1.getIntegralType(), Arg2.getIntegralType()) && llvm::APSInt::isSameValue(Arg1.getAsIntegral(), Arg2.getAsIntegral()); >From 4e6ef2d94490ff84b9f4f45a07898472a219b62d Mon Sep 17 00:00:00 2001 From: BaLiKfromUA <[email protected]> Date: Wed, 23 Sep 2026 22:56:07 +0100 Subject: [PATCH 4/4] Add more precise reference to wording --- clang/lib/AST/ASTContext.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 799f2bfc48a80..c6087cf4ba98d 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -8040,8 +8040,9 @@ bool ASTContext::isSameTemplateArgument(const TemplateArgument &Arg1, getCanonicalTemplateName(Arg2.getAsTemplateOrTemplatePattern()); case TemplateArgument::Integral: - // The types have to match as well as the values: - // C++ [temp.type]p2 + // Per C++20 [temp.type]p2: + // "Two values are template-argument-equivalent if they are of the same type + // and they are of integral type and their values are the same". return hasSameType(Arg1.getIntegralType(), Arg2.getIntegralType()) && llvm::APSInt::isSameValue(Arg1.getAsIntegral(), Arg2.getAsIntegral()); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
