On Fri, 20 Feb 2026, Jason Merrill wrote:
> On 2/14/26 12:19 AM, Patrick Palka wrote:
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this approach
> > seem reasonable?
> >
> > -- >8 --
> >
> > In the first testcase below (pretending r16-7491 hasn't been merged and
> > we still do ad-hoc checking of simple constrained auto NTTPs), we find
> > ourselves conflating two dependent specializations of the form foo<c>
> > where in one c is a constrained auto NTTP with original level 3, and in
> > the other with original level 2.
>
> Could we not conflate them? I wonder about in structural_comptypes just never
> considering constrained autos to be equivalent.
Ah, but that'd break redeclaration matching:
template<C auto* V> void f();
template<C auto* V> void f(); // now distinct overload instead of redecl
Luckily, PLACEHOLDER_TYPE_CONSTRAINTS_INFO also contains the in-scope
set of template parameters, so we can also compare those and treat
constrained autos as equivalent only if they're from compatible template
contexts. Like so?
-- >8 --
Subject: [PATCH] c++: equivalence of lowered constrained auto NTTPs [PR120136]
PR c++/120136
gcc/cp/ChangeLog:
* typeck.cc (structural_comptypes) <case TEMPLATE_TYPE_PARM>:
Return false constrained autos from incompatible template contexts.
gcc/testsuite/ChangeLog:
* g++.dg/cpp2a/concepts-placeholder16.C: New test.
* g++.dg/cpp2a/concepts-placeholder16b.C: New test.
* g++.dg/cpp2a/concepts-placeholder17.C: New test.
* g++.dg/cpp2a/concepts-placeholder17b.C: New test.
---
gcc/cp/typeck.cc | 17 ++++++++--
.../g++.dg/cpp2a/concepts-placeholder16.C | 24 ++++++++++++++
.../g++.dg/cpp2a/concepts-placeholder16b.C | 27 ++++++++++++++++
.../g++.dg/cpp2a/concepts-placeholder17.C | 29 +++++++++++++++++
.../g++.dg/cpp2a/concepts-placeholder17b.C | 32 +++++++++++++++++++
5 files changed, 126 insertions(+), 3 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder16.C
create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder16b.C
create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder17.C
create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder17b.C
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 79eb3b5ba286..885109767ce9 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -1604,9 +1604,20 @@ structural_comptypes (tree t1, tree t2, int strict)
CLASS_PLACEHOLDER_TEMPLATE (t2)))
return false;
/* Constrained 'auto's are distinct from parms that don't have the same
- constraints. */
- if (!equivalent_placeholder_constraints (t1, t2))
- return false;
+ constraints or are from incompatible template contexts
(do_auto_deduction
+ is sensitive to their depth). */
+ if (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t1)
+ || PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t2))
+ {
+ if (!equivalent_placeholder_constraints (t1, t2))
+ return false;
+ tree parms1 = TREE_PURPOSE (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t1));
+ tree parms2 = TREE_PURPOSE (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (t2));
+ int depth1 = parms1 ? TMPL_PARMS_DEPTH (parms1) : 0;
+ int depth2 = parms2 ? TMPL_PARMS_DEPTH (parms2) : 0;
+ if (depth1 != depth2)
+ return false;
+ }
break;
case TYPENAME_TYPE:
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder16.C
b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder16.C
new file mode 100644
index 000000000000..bc29f8ed0e96
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder16.C
@@ -0,0 +1,24 @@
+// PR c++/120136
+// { dg-do compile { target c++20 } }
+
+template <class>
+concept car = true;
+
+template <auto... xs>
+struct set {
+ static auto contains(car auto x) {
+ return ((x == xs) || ...);
+ }
+};
+
+template <auto>
+struct foo {
+ template <car auto c>
+ friend bool operator==(foo, foo<c>);
+};
+
+template <car auto c> foo<c> foobar;
+
+auto a = foobar<42>;
+auto b = set<a>{};
+auto c = b.contains(a);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder16b.C
b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder16b.C
new file mode 100644
index 000000000000..e62a3510d2ae
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder16b.C
@@ -0,0 +1,27 @@
+// PR c++/120136
+// { dg-do compile { target c++20 } }
+// A version of concepts-placeholder16.C using where the
+// constrained auto NTTP 'c' is an auto&.
+
+template <class>
+concept car = true;
+
+template <auto... xs>
+struct set {
+ static auto contains(car auto x) {
+ return ((x == xs) || ...);
+ }
+};
+
+template <auto>
+struct foo {
+ template <car auto& c>
+ friend bool operator==(foo, foo<c>);
+};
+
+template <car auto& c> foo<c> foobar;
+
+inline constexpr int n = 42;
+auto a = foobar<n>;
+auto b = set<a>{};
+auto c = b.contains(a);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder17.C
b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder17.C
new file mode 100644
index 000000000000..de8de1b74d6e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder17.C
@@ -0,0 +1,29 @@
+// PR c++/122204
+// { dg-do compile { target c++20 } }
+
+template <int param>
+struct A {};
+
+template <typename T>
+struct IsA {
+ static constexpr bool value = true;
+};
+
+template <typename T>
+concept Ac = IsA<T>::value;
+
+template <Ac auto dim>
+struct V {};
+
+template <typename T>
+struct IsV {};
+template <Ac auto dim>
+struct IsV<V<dim>> {};
+
+template <int i>
+struct B {
+ template <Ac auto dim>
+ void f(V<dim>) const;
+};
+
+void crashing_function() { B<0>{}.f(V<A<0>{}>{}); }
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder17b.C
b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder17b.C
new file mode 100644
index 000000000000..fc8e2a6bb0fc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder17b.C
@@ -0,0 +1,32 @@
+// PR c++/122204
+// { dg-do compile { target c++20 } }
+// A version of concepts-placeholder16.C using where the
+// constrained auto NTTP 'dim' is an auto&.
+
+template <int param>
+struct A {};
+
+template <typename T>
+struct IsA {
+ static constexpr bool value = true;
+};
+
+template <typename T>
+concept Ac = IsA<T>::value;
+
+template <Ac auto& dim>
+struct V {};
+
+template <typename T>
+struct IsV {};
+template <Ac auto& dim>
+struct IsV<V<dim>> {};
+
+template <int i>
+struct B {
+ template <Ac auto& dim>
+ void f(V<dim>) const;
+};
+
+inline constexpr A<0> a;
+void crashing_function() { B<0>{}.f(V<a>{}); }
--
2.53.0.749.g5361983c07