[clang] [clang] Fix partial specialization matching for NTTPs of placeholder type (PR #225239)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 22 00:57:35 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Valentyn Yukhymenko (BaLiKfromUA)
<details>
<summary>Changes</summary>
Fixes https://github.com/llvm/llvm-project/issues/124186
Fixes https://github.com/llvm/llvm-project/issues/42421
Fixes https://github.com/llvm/llvm-project/issues/53982
----
🔎 [**Compiler Explorer Link** to illustrate differences between GCC and Clang trunks](https://godbolt.org/z/MY4r8bMx8)
**🤖 AI usage:** I used LLM to suggest test cases to improve coverage + to find existing open issues which my fix might address.
**Note about my fix:** I checked existing callers of `ASTContext::isSameTemplateArgument` and I _think_ my change is safe.
But please correct me if I am wrong! I will move the check somewhere to the relevant caller side then.
---
Full diff: https://github.com/llvm/llvm-project/pull/225239.diff
3 Files Affected:
- (modified) clang/docs/ReleaseNotes.md (+7)
- (modified) clang/lib/AST/ASTContext.cpp (+4-1)
- (modified) clang/test/SemaTemplate/temp_arg_nontype_cxx1z.cpp (+148-14)
``````````diff
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 303f972fcaae1..53ad36c840e71 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -737,6 +737,13 @@ features cannot lower the translation-unit ABI level;
lookup before asserting that the name is not dependent, avoiding an assertion
after an earlier diagnostic has caused the declaration to be unavailable. (#GH220525)
+- 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 e74423ca8c8a1..799f2bfc48a80 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -8040,7 +8040,10 @@ 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
+ 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..1472aaa632400 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}}
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}}
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 { };
``````````
</details>
https://github.com/llvm/llvm-project/pull/225239
More information about the cfe-commits
mailing list