diff mbox series

c++: explicit spec of constrained member tmpl [PR107522]

Message ID 20221201163752.2176490-1-ppalka@redhat.com
State New
Headers show
Series c++: explicit spec of constrained member tmpl [PR107522] | expand

Commit Message

Patrick Palka Dec. 1, 2022, 4:37 p.m. UTC
When defining a explicit specialization of a constrained member template
(of a class template) such as f and g in the below testcase, the
DECL_TEMPLATE_PARMS of the corresponding TEMPLATE_DECL are partially
instantiated, whereas its associated constraints are carried over
from the original template and thus are in terms of the original
DECL_TEMPLATE_PARMS.  So during normalization for such an explicit
specialization we need to consider the (parameters of) the most general
template, since that's what the constraints are in terms of and since we
always use the full set of template arguments during satisfaction.

Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
trunk and perhaps 12?

	PR c++/107522

gcc/cp/ChangeLog:

	* constraint.cc (get_normalized_constraints_from_decl): Use the
	most general template for an explicit specialization of a
	member template.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/concepts-explicit-spec7.C: New test.
---
 gcc/cp/constraint.cc                          | 18 ++++++++---
 .../g++.dg/cpp2a/concepts-explicit-spec7.C    | 31 +++++++++++++++++++
 2 files changed, 44 insertions(+), 5 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C

Comments

Jason Merrill Dec. 1, 2022, 7:15 p.m. UTC | #1
On 12/1/22 11:37, Patrick Palka wrote:
> When defining a explicit specialization of a constrained member template
> (of a class template) such as f and g in the below testcase, the
> DECL_TEMPLATE_PARMS of the corresponding TEMPLATE_DECL are partially
> instantiated, whereas its associated constraints are carried over
> from the original template and thus are in terms of the original
> DECL_TEMPLATE_PARMS.

But why are they carried over?  We wrote a specification of the 
constraints in terms of the temprate parameters of the specialization, 
why are we throwing that away?

> So during normalization for such an explicit
> specialization we need to consider the (parameters of) the most general
> template, since that's what the constraints are in terms of and since we
> always use the full set of template arguments during satisfaction.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> trunk and perhaps 12?
> 
> 	PR c++/107522
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (get_normalized_constraints_from_decl): Use the
> 	most general template for an explicit specialization of a
> 	member template.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp2a/concepts-explicit-spec7.C: New test.
> ---
>   gcc/cp/constraint.cc                          | 18 ++++++++---
>   .../g++.dg/cpp2a/concepts-explicit-spec7.C    | 31 +++++++++++++++++++
>   2 files changed, 44 insertions(+), 5 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index ab0f66b3d7e..f1df84c2a1c 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -973,11 +973,19 @@ get_normalized_constraints_from_decl (tree d, bool diag = false)
>        accepting the latter causes the template parameter level of U
>        to be reduced in a way that makes it overly difficult substitute
>        concrete arguments (i.e., eventually {int, int} during satisfaction.  */
> -  if (tmpl)
> -  {
> -    if (DECL_LANG_SPECIFIC(tmpl) && !DECL_TEMPLATE_SPECIALIZATION (tmpl))
> -      tmpl = most_general_template (tmpl);
> -  }
> +  if (tmpl && DECL_LANG_SPECIFIC (tmpl)
> +      && (!DECL_TEMPLATE_SPECIALIZATION (tmpl)
> +	  /* DECL_TEMPLATE_SPECIALIZATION means we're dealing with either a
> +	     partial specialization or an explicit specialization of a member
> +	     template.  In the former case all is well: the constraints are in
> +	     terms in TMPL's parameters.  But in the latter case TMPL's
> +	     parameters are partially instantiated whereas its constraints
> +	     aren't, so we need to consider (the parameters of) the most
> +	     general template.  The following test distinguishes between a
> +	     partial specialization and such an explicit specialization.  */
> +	  || (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (tmpl))
> +	      < TMPL_ARGS_DEPTH (DECL_TI_ARGS (tmpl)))))
> +    tmpl = most_general_template (tmpl);
>   
>     d = tmpl ? tmpl : decl;
>   
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> new file mode 100644
> index 00000000000..5b5a6df20ff
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> @@ -0,0 +1,31 @@
> +// PR c++/107522
> +// { dg-do compile { target c++20 } }
> +
> +template<class T>
> +struct A
> +{
> +  template<int N>
> +  static void f() requires (N == 42);
> +
> +  template<class U>
> +  struct B {
> +    template<int N>
> +    static void g() requires (T(N) == 42);
> +  };
> +};
> +
> +template<>
> +template<int N>
> +void A<int>::f() requires (N == 42) { }
> +
> +template<>
> +template<>
> +template<int N>
> +void A<int>::B<int>::g() requires (int(N) == 42) { }
> +
> +int main() {
> +  A<int>::f<42>();
> +  A<int>::f<43>(); // { dg-error "no match" }
> +  A<int>::B<int>::g<42>();
> +  A<int>::B<int>::g<43>(); // { dg-error "no match" }
> +}
Patrick Palka Dec. 1, 2022, 7:51 p.m. UTC | #2
On Thu, 1 Dec 2022, Jason Merrill wrote:

> On 12/1/22 11:37, Patrick Palka wrote:
> > When defining a explicit specialization of a constrained member template
> > (of a class template) such as f and g in the below testcase, the
> > DECL_TEMPLATE_PARMS of the corresponding TEMPLATE_DECL are partially
> > instantiated, whereas its associated constraints are carried over
> > from the original template and thus are in terms of the original
> > DECL_TEMPLATE_PARMS.
> 
> But why are they carried over?  We wrote a specification of the constraints in
> terms of the temprate parameters of the specialization, why are we throwing
> that away?

Using the partially instantiated constraints would require adding a
special case to satisfaction since during satisfaction we currently
always use the full set of template arguments (relative to the most
general template).  For satisfaction of the partiall instantiated
constraints, we'd instead have to use the template arguments relative to
the explicit specialization, e.g. {42} instead of {{int},{42}} for
A<int>::f<42>.  Not sure if that would be preferable, but it seems
doable.

> 
> > So during normalization for such an explicit
> > specialization we need to consider the (parameters of) the most general
> > template, since that's what the constraints are in terms of and since we
> > always use the full set of template arguments during satisfaction.
> > 
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> > trunk and perhaps 12?
> > 
> > 	PR c++/107522
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* constraint.cc (get_normalized_constraints_from_decl): Use the
> > 	most general template for an explicit specialization of a
> > 	member template.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/cpp2a/concepts-explicit-spec7.C: New test.
> > ---
> >   gcc/cp/constraint.cc                          | 18 ++++++++---
> >   .../g++.dg/cpp2a/concepts-explicit-spec7.C    | 31 +++++++++++++++++++
> >   2 files changed, 44 insertions(+), 5 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> > 
> > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > index ab0f66b3d7e..f1df84c2a1c 100644
> > --- a/gcc/cp/constraint.cc
> > +++ b/gcc/cp/constraint.cc
> > @@ -973,11 +973,19 @@ get_normalized_constraints_from_decl (tree d, bool
> > diag = false)
> >        accepting the latter causes the template parameter level of U
> >        to be reduced in a way that makes it overly difficult substitute
> >        concrete arguments (i.e., eventually {int, int} during satisfaction.
> > */
> > -  if (tmpl)
> > -  {
> > -    if (DECL_LANG_SPECIFIC(tmpl) && !DECL_TEMPLATE_SPECIALIZATION (tmpl))
> > -      tmpl = most_general_template (tmpl);
> > -  }
> > +  if (tmpl && DECL_LANG_SPECIFIC (tmpl)
> > +      && (!DECL_TEMPLATE_SPECIALIZATION (tmpl)
> > +	  /* DECL_TEMPLATE_SPECIALIZATION means we're dealing with either a
> > +	     partial specialization or an explicit specialization of a member
> > +	     template.  In the former case all is well: the constraints are in
> > +	     terms in TMPL's parameters.  But in the latter case TMPL's
> > +	     parameters are partially instantiated whereas its constraints
> > +	     aren't, so we need to consider (the parameters of) the most
> > +	     general template.  The following test distinguishes between a
> > +	     partial specialization and such an explicit specialization.  */
> > +	  || (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (tmpl))
> > +	      < TMPL_ARGS_DEPTH (DECL_TI_ARGS (tmpl)))))
> > +    tmpl = most_general_template (tmpl);
> >       d = tmpl ? tmpl : decl;
> >   diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> > b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> > new file mode 100644
> > index 00000000000..5b5a6df20ff
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> > @@ -0,0 +1,31 @@
> > +// PR c++/107522
> > +// { dg-do compile { target c++20 } }
> > +
> > +template<class T>
> > +struct A
> > +{
> > +  template<int N>
> > +  static void f() requires (N == 42);
> > +
> > +  template<class U>
> > +  struct B {
> > +    template<int N>
> > +    static void g() requires (T(N) == 42);
> > +  };
> > +};
> > +
> > +template<>
> > +template<int N>
> > +void A<int>::f() requires (N == 42) { }
> > +
> > +template<>
> > +template<>
> > +template<int N>
> > +void A<int>::B<int>::g() requires (int(N) == 42) { }
> > +
> > +int main() {
> > +  A<int>::f<42>();
> > +  A<int>::f<43>(); // { dg-error "no match" }
> > +  A<int>::B<int>::g<42>();
> > +  A<int>::B<int>::g<43>(); // { dg-error "no match" }
> > +}
> 
>
Jason Merrill Dec. 1, 2022, 9:17 p.m. UTC | #3
On 12/1/22 14:51, Patrick Palka wrote:
> On Thu, 1 Dec 2022, Jason Merrill wrote:
> 
>> On 12/1/22 11:37, Patrick Palka wrote:
>>> When defining a explicit specialization of a constrained member template
>>> (of a class template) such as f and g in the below testcase, the
>>> DECL_TEMPLATE_PARMS of the corresponding TEMPLATE_DECL are partially
>>> instantiated, whereas its associated constraints are carried over
>>> from the original template and thus are in terms of the original
>>> DECL_TEMPLATE_PARMS.
>>
>> But why are they carried over?  We wrote a specification of the constraints in
>> terms of the template parameters of the specialization, why are we throwing
>> that away?
> 
> Using the partially instantiated constraints would require adding a
> special case to satisfaction since during satisfaction we currently
> always use the full set of template arguments (relative to the most
> general template).

But not for partial specializations, right?  It seems natural to handle 
this explicit instantiation the way we handle partial specializations, 
as both have their constraints written in terms of their template 
parameters.

> For satisfaction of the partially instantiated
> constraints, we'd instead have to use the template arguments relative to
> the explicit specialization, e.g. {42} instead of {{int},{42}} for
> A<int>::f<42>.  Not sure if that would be preferable, but it seems
> doable.
> 
>>
>>> So during normalization for such an explicit
>>> specialization we need to consider the (parameters of) the most general
>>> template, since that's what the constraints are in terms of and since we
>>> always use the full set of template arguments during satisfaction.
>>>
>>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
>>> trunk and perhaps 12?
>>>
>>> 	PR c++/107522
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* constraint.cc (get_normalized_constraints_from_decl): Use the
>>> 	most general template for an explicit specialization of a
>>> 	member template.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	* g++.dg/cpp2a/concepts-explicit-spec7.C: New test.
>>> ---
>>>    gcc/cp/constraint.cc                          | 18 ++++++++---
>>>    .../g++.dg/cpp2a/concepts-explicit-spec7.C    | 31 +++++++++++++++++++
>>>    2 files changed, 44 insertions(+), 5 deletions(-)
>>>    create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
>>>
>>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>>> index ab0f66b3d7e..f1df84c2a1c 100644
>>> --- a/gcc/cp/constraint.cc
>>> +++ b/gcc/cp/constraint.cc
>>> @@ -973,11 +973,19 @@ get_normalized_constraints_from_decl (tree d, bool
>>> diag = false)
>>>         accepting the latter causes the template parameter level of U
>>>         to be reduced in a way that makes it overly difficult substitute
>>>         concrete arguments (i.e., eventually {int, int} during satisfaction.
>>> */
>>> -  if (tmpl)
>>> -  {
>>> -    if (DECL_LANG_SPECIFIC(tmpl) && !DECL_TEMPLATE_SPECIALIZATION (tmpl))
>>> -      tmpl = most_general_template (tmpl);
>>> -  }
>>> +  if (tmpl && DECL_LANG_SPECIFIC (tmpl)
>>> +      && (!DECL_TEMPLATE_SPECIALIZATION (tmpl)
>>> +	  /* DECL_TEMPLATE_SPECIALIZATION means we're dealing with either a
>>> +	     partial specialization or an explicit specialization of a member
>>> +	     template.  In the former case all is well: the constraints are in
>>> +	     terms in TMPL's parameters.  But in the latter case TMPL's
>>> +	     parameters are partially instantiated whereas its constraints
>>> +	     aren't, so we need to consider (the parameters of) the most
>>> +	     general template.  The following test distinguishes between a
>>> +	     partial specialization and such an explicit specialization.  */
>>> +	  || (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (tmpl))
>>> +	      < TMPL_ARGS_DEPTH (DECL_TI_ARGS (tmpl)))))
>>> +    tmpl = most_general_template (tmpl);
>>>        d = tmpl ? tmpl : decl;
>>>    diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
>>> b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
>>> new file mode 100644
>>> index 00000000000..5b5a6df20ff
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
>>> @@ -0,0 +1,31 @@
>>> +// PR c++/107522
>>> +// { dg-do compile { target c++20 } }
>>> +
>>> +template<class T>
>>> +struct A
>>> +{
>>> +  template<int N>
>>> +  static void f() requires (N == 42);
>>> +
>>> +  template<class U>
>>> +  struct B {
>>> +    template<int N>
>>> +    static void g() requires (T(N) == 42);
>>> +  };
>>> +};
>>> +
>>> +template<>
>>> +template<int N>
>>> +void A<int>::f() requires (N == 42) { }
>>> +
>>> +template<>
>>> +template<>
>>> +template<int N>
>>> +void A<int>::B<int>::g() requires (int(N) == 42) { }
>>> +
>>> +int main() {
>>> +  A<int>::f<42>();
>>> +  A<int>::f<43>(); // { dg-error "no match" }
>>> +  A<int>::B<int>::g<42>();
>>> +  A<int>::B<int>::g<43>(); // { dg-error "no match" }
>>> +}
>>
>>
>
Patrick Palka Dec. 2, 2022, 2:30 p.m. UTC | #4
On Thu, 1 Dec 2022, Jason Merrill wrote:

> On 12/1/22 14:51, Patrick Palka wrote:
> > On Thu, 1 Dec 2022, Jason Merrill wrote:
> > 
> > > On 12/1/22 11:37, Patrick Palka wrote:
> > > > When defining a explicit specialization of a constrained member template
> > > > (of a class template) such as f and g in the below testcase, the
> > > > DECL_TEMPLATE_PARMS of the corresponding TEMPLATE_DECL are partially
> > > > instantiated, whereas its associated constraints are carried over
> > > > from the original template and thus are in terms of the original
> > > > DECL_TEMPLATE_PARMS.
> > > 
> > > But why are they carried over?  We wrote a specification of the
> > > constraints in
> > > terms of the template parameters of the specialization, why are we
> > > throwing
> > > that away?
> > 
> > Using the partially instantiated constraints would require adding a
> > special case to satisfaction since during satisfaction we currently
> > always use the full set of template arguments (relative to the most
> > general template).
> 
> But not for partial specializations, right?  It seems natural to handle this
> explicit instantiation the way we handle partial specializations, as both have
> their constraints written in terms of their template parameters.

True, but what about the general rule that we don't partially instantiate
constraints outside of declaration matching?  Checking satisfaction of
partially instantiated constraints here can introduce hard errors during
normalization, e.g.

  template<class T>
  concept C1 = __same_as(T, void);

  template<class T>
  concept C2 = C1<typename T::type>;

  template<int N>
  concept D = (N == 42);

  template<class T>
  struct A {
    template<int N>
    static void f() requires C2<T> || D<N>;
  };

  template<>
  template<int N>
  void A<int>::f() requires C2<int> || D<N> { }

  int main() {
    A<int>::f<42>();
  }

Normalization of the the partially instantiated constraints will give a
hard error due to 'int::type' being ill-formed, whereas the uninstantiated
constraints are fine.

> 
> > For satisfaction of the partially instantiated
> > constraints, we'd instead have to use the template arguments relative to
> > the explicit specialization, e.g. {42} instead of {{int},{42}} for
> > A<int>::f<42>.  Not sure if that would be preferable, but it seems
> > doable.
> > 
> > > 
> > > > So during normalization for such an explicit
> > > > specialization we need to consider the (parameters of) the most general
> > > > template, since that's what the constraints are in terms of and since we
> > > > always use the full set of template arguments during satisfaction.
> > > > 
> > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> > > > trunk and perhaps 12?
> > > > 
> > > > 	PR c++/107522
> > > > 
> > > > gcc/cp/ChangeLog:
> > > > 
> > > > 	* constraint.cc (get_normalized_constraints_from_decl): Use the
> > > > 	most general template for an explicit specialization of a
> > > > 	member template.
> > > > 
> > > > gcc/testsuite/ChangeLog:
> > > > 
> > > > 	* g++.dg/cpp2a/concepts-explicit-spec7.C: New test.
> > > > ---
> > > >    gcc/cp/constraint.cc                          | 18 ++++++++---
> > > >    .../g++.dg/cpp2a/concepts-explicit-spec7.C    | 31
> > > > +++++++++++++++++++
> > > >    2 files changed, 44 insertions(+), 5 deletions(-)
> > > >    create mode 100644
> > > > gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> > > > 
> > > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > > > index ab0f66b3d7e..f1df84c2a1c 100644
> > > > --- a/gcc/cp/constraint.cc
> > > > +++ b/gcc/cp/constraint.cc
> > > > @@ -973,11 +973,19 @@ get_normalized_constraints_from_decl (tree d, bool
> > > > diag = false)
> > > >         accepting the latter causes the template parameter level of U
> > > >         to be reduced in a way that makes it overly difficult substitute
> > > >         concrete arguments (i.e., eventually {int, int} during
> > > > satisfaction.
> > > > */
> > > > -  if (tmpl)
> > > > -  {
> > > > -    if (DECL_LANG_SPECIFIC(tmpl) && !DECL_TEMPLATE_SPECIALIZATION
> > > > (tmpl))
> > > > -      tmpl = most_general_template (tmpl);
> > > > -  }
> > > > +  if (tmpl && DECL_LANG_SPECIFIC (tmpl)
> > > > +      && (!DECL_TEMPLATE_SPECIALIZATION (tmpl)
> > > > +	  /* DECL_TEMPLATE_SPECIALIZATION means we're dealing with either a
> > > > +	     partial specialization or an explicit specialization of a member
> > > > +	     template.  In the former case all is well: the constraints are in
> > > > +	     terms in TMPL's parameters.  But in the latter case TMPL's
> > > > +	     parameters are partially instantiated whereas its constraints
> > > > +	     aren't, so we need to consider (the parameters of) the most
> > > > +	     general template.  The following test distinguishes between a
> > > > +	     partial specialization and such an explicit specialization.  */
> > > > +	  || (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (tmpl))
> > > > +	      < TMPL_ARGS_DEPTH (DECL_TI_ARGS (tmpl)))))
> > > > +    tmpl = most_general_template (tmpl);
> > > >        d = tmpl ? tmpl : decl;
> > > >    diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> > > > b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> > > > new file mode 100644
> > > > index 00000000000..5b5a6df20ff
> > > > --- /dev/null
> > > > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> > > > @@ -0,0 +1,31 @@
> > > > +// PR c++/107522
> > > > +// { dg-do compile { target c++20 } }
> > > > +
> > > > +template<class T>
> > > > +struct A
> > > > +{
> > > > +  template<int N>
> > > > +  static void f() requires (N == 42);
> > > > +
> > > > +  template<class U>
> > > > +  struct B {
> > > > +    template<int N>
> > > > +    static void g() requires (T(N) == 42);
> > > > +  };
> > > > +};
> > > > +
> > > > +template<>
> > > > +template<int N>
> > > > +void A<int>::f() requires (N == 42) { }
> > > > +
> > > > +template<>
> > > > +template<>
> > > > +template<int N>
> > > > +void A<int>::B<int>::g() requires (int(N) == 42) { }
> > > > +
> > > > +int main() {
> > > > +  A<int>::f<42>();
> > > > +  A<int>::f<43>(); // { dg-error "no match" }
> > > > +  A<int>::B<int>::g<42>();
> > > > +  A<int>::B<int>::g<43>(); // { dg-error "no match" }
> > > > +}
> > > 
> > > 
> > 
> 
>
Jason Merrill Dec. 2, 2022, 4:16 p.m. UTC | #5
On 12/2/22 09:30, Patrick Palka wrote:
> On Thu, 1 Dec 2022, Jason Merrill wrote:
> 
>> On 12/1/22 14:51, Patrick Palka wrote:
>>> On Thu, 1 Dec 2022, Jason Merrill wrote:
>>>
>>>> On 12/1/22 11:37, Patrick Palka wrote:
>>>>> When defining a explicit specialization of a constrained member template
>>>>> (of a class template) such as f and g in the below testcase, the
>>>>> DECL_TEMPLATE_PARMS of the corresponding TEMPLATE_DECL are partially
>>>>> instantiated, whereas its associated constraints are carried over
>>>>> from the original template and thus are in terms of the original
>>>>> DECL_TEMPLATE_PARMS.
>>>>
>>>> But why are they carried over?  We wrote a specification of the
>>>> constraints in
>>>> terms of the template parameters of the specialization, why are we
>>>> throwing
>>>> that away?
>>>
>>> Using the partially instantiated constraints would require adding a
>>> special case to satisfaction since during satisfaction we currently
>>> always use the full set of template arguments (relative to the most
>>> general template).
>>
>> But not for partial specializations, right?  It seems natural to handle this
>> explicit instantiation the way we handle partial specializations, as both have
>> their constraints written in terms of their template parameters.
> 
> True, but what about the general rule that we don't partially instantiate
> constraints outside of declaration matching?  Checking satisfaction of
> partially instantiated constraints here can introduce hard errors during
> normalization, e.g.
> 
>    template<class T>
>    concept C1 = __same_as(T, void);
> 
>    template<class T>
>    concept C2 = C1<typename T::type>;
> 
>    template<int N>
>    concept D = (N == 42);
> 
>    template<class T>
>    struct A {
>      template<int N>
>      static void f() requires C2<T> || D<N>;
>    };
> 
>    template<>
>    template<int N>
>    void A<int>::f() requires C2<int> || D<N> { }
> 
>    int main() {
>      A<int>::f<42>();
>    }
> 
> Normalization of the the partially instantiated constraints will give a
> hard error due to 'int::type' being ill-formed, whereas the uninstantiated
> constraints are fine.

Hmm, interesting point, but in this example that happens because the 
specialization is nonsensical: we wouldn't be normalizing the 
partially-instantiated constraints so much as the ones that the user 
explicitly wrote, so a hard error seems justified.

>>> For satisfaction of the partially instantiated
>>> constraints, we'd instead have to use the template arguments relative to
>>> the explicit specialization, e.g. {42} instead of {{int},{42}} for
>>> A<int>::f<42>.  Not sure if that would be preferable, but it seems
>>> doable.
>>>
>>>>
>>>>> So during normalization for such an explicit
>>>>> specialization we need to consider the (parameters of) the most general
>>>>> template, since that's what the constraints are in terms of and since we
>>>>> always use the full set of template arguments during satisfaction.
>>>>>
>>>>> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
>>>>> trunk and perhaps 12?
>>>>>
>>>>> 	PR c++/107522
>>>>>
>>>>> gcc/cp/ChangeLog:
>>>>>
>>>>> 	* constraint.cc (get_normalized_constraints_from_decl): Use the
>>>>> 	most general template for an explicit specialization of a
>>>>> 	member template.
>>>>>
>>>>> gcc/testsuite/ChangeLog:
>>>>>
>>>>> 	* g++.dg/cpp2a/concepts-explicit-spec7.C: New test.
>>>>> ---
>>>>>     gcc/cp/constraint.cc                          | 18 ++++++++---
>>>>>     .../g++.dg/cpp2a/concepts-explicit-spec7.C    | 31
>>>>> +++++++++++++++++++
>>>>>     2 files changed, 44 insertions(+), 5 deletions(-)
>>>>>     create mode 100644
>>>>> gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
>>>>>
>>>>> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
>>>>> index ab0f66b3d7e..f1df84c2a1c 100644
>>>>> --- a/gcc/cp/constraint.cc
>>>>> +++ b/gcc/cp/constraint.cc
>>>>> @@ -973,11 +973,19 @@ get_normalized_constraints_from_decl (tree d, bool
>>>>> diag = false)
>>>>>          accepting the latter causes the template parameter level of U
>>>>>          to be reduced in a way that makes it overly difficult substitute
>>>>>          concrete arguments (i.e., eventually {int, int} during
>>>>> satisfaction.
>>>>> */
>>>>> -  if (tmpl)
>>>>> -  {
>>>>> -    if (DECL_LANG_SPECIFIC(tmpl) && !DECL_TEMPLATE_SPECIALIZATION
>>>>> (tmpl))
>>>>> -      tmpl = most_general_template (tmpl);
>>>>> -  }
>>>>> +  if (tmpl && DECL_LANG_SPECIFIC (tmpl)
>>>>> +      && (!DECL_TEMPLATE_SPECIALIZATION (tmpl)
>>>>> +	  /* DECL_TEMPLATE_SPECIALIZATION means we're dealing with either a
>>>>> +	     partial specialization or an explicit specialization of a member
>>>>> +	     template.  In the former case all is well: the constraints are in
>>>>> +	     terms in TMPL's parameters.  But in the latter case TMPL's
>>>>> +	     parameters are partially instantiated whereas its constraints
>>>>> +	     aren't, so we need to consider (the parameters of) the most
>>>>> +	     general template.  The following test distinguishes between a
>>>>> +	     partial specialization and such an explicit specialization.  */
>>>>> +	  || (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (tmpl))
>>>>> +	      < TMPL_ARGS_DEPTH (DECL_TI_ARGS (tmpl)))))
>>>>> +    tmpl = most_general_template (tmpl);
>>>>>         d = tmpl ? tmpl : decl;
>>>>>     diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
>>>>> b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
>>>>> new file mode 100644
>>>>> index 00000000000..5b5a6df20ff
>>>>> --- /dev/null
>>>>> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
>>>>> @@ -0,0 +1,31 @@
>>>>> +// PR c++/107522
>>>>> +// { dg-do compile { target c++20 } }
>>>>> +
>>>>> +template<class T>
>>>>> +struct A
>>>>> +{
>>>>> +  template<int N>
>>>>> +  static void f() requires (N == 42);
>>>>> +
>>>>> +  template<class U>
>>>>> +  struct B {
>>>>> +    template<int N>
>>>>> +    static void g() requires (T(N) == 42);
>>>>> +  };
>>>>> +};
>>>>> +
>>>>> +template<>
>>>>> +template<int N>
>>>>> +void A<int>::f() requires (N == 42) { }
>>>>> +
>>>>> +template<>
>>>>> +template<>
>>>>> +template<int N>
>>>>> +void A<int>::B<int>::g() requires (int(N) == 42) { }
>>>>> +
>>>>> +int main() {
>>>>> +  A<int>::f<42>();
>>>>> +  A<int>::f<43>(); // { dg-error "no match" }
>>>>> +  A<int>::B<int>::g<42>();
>>>>> +  A<int>::B<int>::g<43>(); // { dg-error "no match" }
>>>>> +}
>>>>
>>>>
>>>
>>
>>
>
Patrick Palka Sept. 12, 2024, 5:07 p.m. UTC | #6
(Sorry to resurrect this thread so late, I lost track of this patch...)

On Fri, 2 Dec 2022, Jason Merrill wrote:

> On 12/2/22 09:30, Patrick Palka wrote:
> > On Thu, 1 Dec 2022, Jason Merrill wrote:
> > 
> > > On 12/1/22 14:51, Patrick Palka wrote:
> > > > On Thu, 1 Dec 2022, Jason Merrill wrote:
> > > > 
> > > > > On 12/1/22 11:37, Patrick Palka wrote:
> > > > > > When defining a explicit specialization of a constrained member
> > > > > > template
> > > > > > (of a class template) such as f and g in the below testcase, the
> > > > > > DECL_TEMPLATE_PARMS of the corresponding TEMPLATE_DECL are partially
> > > > > > instantiated, whereas its associated constraints are carried over
> > > > > > from the original template and thus are in terms of the original
> > > > > > DECL_TEMPLATE_PARMS.
> > > > > 
> > > > > But why are they carried over?  We wrote a specification of the
> > > > > constraints in
> > > > > terms of the template parameters of the specialization, why are we
> > > > > throwing
> > > > > that away?
> > > > 
> > > > Using the partially instantiated constraints would require adding a
> > > > special case to satisfaction since during satisfaction we currently
> > > > always use the full set of template arguments (relative to the most
> > > > general template).
> > > 
> > > But not for partial specializations, right?  It seems natural to handle
> > > this
> > > explicit instantiation the way we handle partial specializations, as both
> > > have
> > > their constraints written in terms of their template parameters.
> > 
> > True, but what about the general rule that we don't partially instantiate
> > constraints outside of declaration matching?  Checking satisfaction of
> > partially instantiated constraints here can introduce hard errors during
> > normalization, e.g.
> > 
> >    template<class T>
> >    concept C1 = __same_as(T, void);
> > 
> >    template<class T>
> >    concept C2 = C1<typename T::type>;
> > 
> >    template<int N>
> >    concept D = (N == 42);
> > 
> >    template<class T>
> >    struct A {
> >      template<int N>
> >      static void f() requires C2<T> || D<N>;
> >    };
> > 
> >    template<>
> >    template<int N>
> >    void A<int>::f() requires C2<int> || D<N> { }
> > 
> >    int main() {
> >      A<int>::f<42>();
> >    }
> > 
> > Normalization of the the partially instantiated constraints will give a
> > hard error due to 'int::type' being ill-formed, whereas the uninstantiated
> > constraints are fine.
> 
> Hmm, interesting point, but in this example that happens because the
> specialization is nonsensical: we wouldn't be normalizing the
> partially-instantiated constraints so much as the ones that the user
> explicitly wrote, so a hard error seems justified.

While the written partially-instantiated constraints are nonsensical,
aren't they only needed for sake of declaration matching?  It doesn't
seem to necessarily imply that that form of constraints is what should
prevail.  This is where the analogy with partial specializations breaks
down IMHO: partial specializations own their constraints.

Implementing your desired approach isn't so bad either however.  We
mainly just need to correct for TI_ARGS being relative to the primary
template rather than the partially instantiated template.  Something
like the following?

-- >8 --

Subject: [PATCH] c++: explicit spec of constrained member tmpl [PR107522]

	PR c++/107522

gcc/cp/ChangeLog:

	* constraint.cc (satisfy_declaration_constraints): Remove
	extraneous outer arguments for a partial or explicit
	specialization.
	* pt.cc (determine_specialization): For an explicit
	specialization of a member template, make the partially
	instantiated constraints prevail.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/concepts-explicit-spec7.C: New test.
---
 gcc/cp/constraint.cc                          | 14 ++++++++-
 gcc/cp/pt.cc                                  |  7 ++++-
 .../g++.dg/cpp2a/concepts-explicit-spec7.C    | 30 +++++++++++++++++++
 3 files changed, 49 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index ebfcdefd284..4dc4fedc659 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -2728,6 +2728,11 @@ satisfy_declaration_constraints (tree t, sat_info info)
       args = TI_ARGS (ti);
       if (inh_ctor_targs)
 	args = add_outermost_template_args (args, inh_ctor_targs);
+      if (DECL_TEMPLATE_SPECIALIZATION (TI_TEMPLATE (ti)))
+	{
+	  tree parms = DECL_TEMPLATE_PARMS (TI_TEMPLATE (ti));
+	  args = get_innermost_template_args (args, TMPL_PARMS_DEPTH (parms));
+	}
     }
 
   if (regenerated_lambda_fn_p (t))
@@ -2811,7 +2816,14 @@ satisfy_declaration_constraints (tree t, tree args, sat_info info)
       args = add_to_template_args (outer_args, args);
     }
   else
-    args = add_outermost_template_args (t, args);
+    {
+      args = add_outermost_template_args (t, args);
+      if (DECL_TEMPLATE_SPECIALIZATION (t))
+	{
+	  tree parms = DECL_TEMPLATE_PARMS (t);
+	  args = get_innermost_template_args (args, TMPL_PARMS_DEPTH (parms));
+	}
+    }
 
   /* If the innermost arguments are dependent, or if the outer arguments
      are dependent and are needed by the constraints, we can't check
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 310e5dfff03..04987f66746 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -2502,7 +2502,12 @@ determine_specialization (tree template_id,
       *targs_out = copy_node (DECL_TI_ARGS (fn));
 
       /* Propagate the candidate's constraints to the declaration.  */
-      if (tsk != tsk_template)
+      if (tsk == tsk_template)
+	{
+	  remove_constraints (fn);
+	  set_constraints (fn, get_constraints (decl));
+	}
+      else
 	set_constraints (decl, get_constraints (fn));
 
       /* DECL is a re-declaration or partial instantiation of a template
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
new file mode 100644
index 00000000000..9452159faf7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
@@ -0,0 +1,30 @@
+// PR c++/107522
+// { dg-do compile { target c++20 } }
+
+template<class T>
+struct A {
+  template<int N>
+  static void f() requires (N == 42);
+
+  template<class U>
+  struct B {
+    template<int N>
+    static void g() requires (T(N) == 42);
+  };
+};
+
+template<>
+template<int N>
+void A<int>::f() requires (N == 42) { }
+
+template<>
+template<>
+template<int N>
+void A<int>::B<int>::g() requires (int(N) == 42) { }
+
+int main() {
+  A<int>::f<42>();
+  A<int>::f<43>(); // { dg-error "no match" }
+  A<int>::B<int>::g<42>();
+  A<int>::B<int>::g<43>(); // { dg-error "no match" }
+}
Patrick Palka Oct. 1, 2024, 1:27 p.m. UTC | #7
On Thu, 12 Sep 2024, Patrick Palka wrote:

> (Sorry to resurrect this thread so late, I lost track of this patch...)
> 
> On Fri, 2 Dec 2022, Jason Merrill wrote:
> 
> > On 12/2/22 09:30, Patrick Palka wrote:
> > > On Thu, 1 Dec 2022, Jason Merrill wrote:
> > > 
> > > > On 12/1/22 14:51, Patrick Palka wrote:
> > > > > On Thu, 1 Dec 2022, Jason Merrill wrote:
> > > > > 
> > > > > > On 12/1/22 11:37, Patrick Palka wrote:
> > > > > > > When defining a explicit specialization of a constrained member
> > > > > > > template
> > > > > > > (of a class template) such as f and g in the below testcase, the
> > > > > > > DECL_TEMPLATE_PARMS of the corresponding TEMPLATE_DECL are partially
> > > > > > > instantiated, whereas its associated constraints are carried over
> > > > > > > from the original template and thus are in terms of the original
> > > > > > > DECL_TEMPLATE_PARMS.
> > > > > > 
> > > > > > But why are they carried over?  We wrote a specification of the
> > > > > > constraints in
> > > > > > terms of the template parameters of the specialization, why are we
> > > > > > throwing
> > > > > > that away?
> > > > > 
> > > > > Using the partially instantiated constraints would require adding a
> > > > > special case to satisfaction since during satisfaction we currently
> > > > > always use the full set of template arguments (relative to the most
> > > > > general template).
> > > > 
> > > > But not for partial specializations, right?  It seems natural to handle
> > > > this
> > > > explicit instantiation the way we handle partial specializations, as both
> > > > have
> > > > their constraints written in terms of their template parameters.
> > > 
> > > True, but what about the general rule that we don't partially instantiate
> > > constraints outside of declaration matching?  Checking satisfaction of
> > > partially instantiated constraints here can introduce hard errors during
> > > normalization, e.g.
> > > 
> > >    template<class T>
> > >    concept C1 = __same_as(T, void);
> > > 
> > >    template<class T>
> > >    concept C2 = C1<typename T::type>;
> > > 
> > >    template<int N>
> > >    concept D = (N == 42);
> > > 
> > >    template<class T>
> > >    struct A {
> > >      template<int N>
> > >      static void f() requires C2<T> || D<N>;
> > >    };
> > > 
> > >    template<>
> > >    template<int N>
> > >    void A<int>::f() requires C2<int> || D<N> { }
> > > 
> > >    int main() {
> > >      A<int>::f<42>();
> > >    }
> > > 
> > > Normalization of the the partially instantiated constraints will give a
> > > hard error due to 'int::type' being ill-formed, whereas the uninstantiated
> > > constraints are fine.
> > 
> > Hmm, interesting point, but in this example that happens because the
> > specialization is nonsensical: we wouldn't be normalizing the
> > partially-instantiated constraints so much as the ones that the user
> > explicitly wrote, so a hard error seems justified.
> 
> While the written partially-instantiated constraints are nonsensical,
> aren't they only needed for sake of declaration matching?  It doesn't
> seem to necessarily imply that that form of constraints is what should
> prevail.  This is where the analogy with partial specializations breaks
> down IMHO: partial specializations own their constraints.
> 
> Implementing your desired approach isn't so bad either however.  We
> mainly just need to correct for TI_ARGS being relative to the primary
> template rather than the partially instantiated template.  Something
> like the following?

Ping.  Not sure if the original approach or this one is preferable?

> 
> -- >8 --
> 
> Subject: [PATCH] c++: explicit spec of constrained member tmpl [PR107522]
> 
> 	PR c++/107522
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (satisfy_declaration_constraints): Remove
> 	extraneous outer arguments for a partial or explicit
> 	specialization.
> 	* pt.cc (determine_specialization): For an explicit
> 	specialization of a member template, make the partially
> 	instantiated constraints prevail.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp2a/concepts-explicit-spec7.C: New test.
> ---
>  gcc/cp/constraint.cc                          | 14 ++++++++-
>  gcc/cp/pt.cc                                  |  7 ++++-
>  .../g++.dg/cpp2a/concepts-explicit-spec7.C    | 30 +++++++++++++++++++
>  3 files changed, 49 insertions(+), 2 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index ebfcdefd284..4dc4fedc659 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -2728,6 +2728,11 @@ satisfy_declaration_constraints (tree t, sat_info info)
>        args = TI_ARGS (ti);
>        if (inh_ctor_targs)
>  	args = add_outermost_template_args (args, inh_ctor_targs);
> +      if (DECL_TEMPLATE_SPECIALIZATION (TI_TEMPLATE (ti)))
> +	{
> +	  tree parms = DECL_TEMPLATE_PARMS (TI_TEMPLATE (ti));
> +	  args = get_innermost_template_args (args, TMPL_PARMS_DEPTH (parms));
> +	}
>      }
>  
>    if (regenerated_lambda_fn_p (t))
> @@ -2811,7 +2816,14 @@ satisfy_declaration_constraints (tree t, tree args, sat_info info)
>        args = add_to_template_args (outer_args, args);
>      }
>    else
> -    args = add_outermost_template_args (t, args);
> +    {
> +      args = add_outermost_template_args (t, args);
> +      if (DECL_TEMPLATE_SPECIALIZATION (t))
> +	{
> +	  tree parms = DECL_TEMPLATE_PARMS (t);
> +	  args = get_innermost_template_args (args, TMPL_PARMS_DEPTH (parms));
> +	}
> +    }
>  
>    /* If the innermost arguments are dependent, or if the outer arguments
>       are dependent and are needed by the constraints, we can't check
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 310e5dfff03..04987f66746 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -2502,7 +2502,12 @@ determine_specialization (tree template_id,
>        *targs_out = copy_node (DECL_TI_ARGS (fn));
>  
>        /* Propagate the candidate's constraints to the declaration.  */
> -      if (tsk != tsk_template)
> +      if (tsk == tsk_template)
> +	{
> +	  remove_constraints (fn);
> +	  set_constraints (fn, get_constraints (decl));
> +	}
> +      else
>  	set_constraints (decl, get_constraints (fn));
>  
>        /* DECL is a re-declaration or partial instantiation of a template
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> new file mode 100644
> index 00000000000..9452159faf7
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> @@ -0,0 +1,30 @@
> +// PR c++/107522
> +// { dg-do compile { target c++20 } }
> +
> +template<class T>
> +struct A {
> +  template<int N>
> +  static void f() requires (N == 42);
> +
> +  template<class U>
> +  struct B {
> +    template<int N>
> +    static void g() requires (T(N) == 42);
> +  };
> +};
> +
> +template<>
> +template<int N>
> +void A<int>::f() requires (N == 42) { }
> +
> +template<>
> +template<>
> +template<int N>
> +void A<int>::B<int>::g() requires (int(N) == 42) { }
> +
> +int main() {
> +  A<int>::f<42>();
> +  A<int>::f<43>(); // { dg-error "no match" }
> +  A<int>::B<int>::g<42>();
> +  A<int>::B<int>::g<43>(); // { dg-error "no match" }
> +}
> -- 
> 2.46.0.551.gc5ee8f2d1c
> 
> 
> > 
> > > > > For satisfaction of the partially instantiated
> > > > > constraints, we'd instead have to use the template arguments relative to
> > > > > the explicit specialization, e.g. {42} instead of {{int},{42}} for
> > > > > A<int>::f<42>.  Not sure if that would be preferable, but it seems
> > > > > doable.
> > > > > 
> > > > > > 
> > > > > > > So during normalization for such an explicit
> > > > > > > specialization we need to consider the (parameters of) the most
> > > > > > > general
> > > > > > > template, since that's what the constraints are in terms of and
> > > > > > > since we
> > > > > > > always use the full set of template arguments during satisfaction.
> > > > > > > 
> > > > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK
> > > > > > > for
> > > > > > > trunk and perhaps 12?
> > > > > > > 
> > > > > > > 	PR c++/107522
> > > > > > > 
> > > > > > > gcc/cp/ChangeLog:
> > > > > > > 
> > > > > > > 	* constraint.cc (get_normalized_constraints_from_decl): Use
> > > > > > > the
> > > > > > > 	most general template for an explicit specialization of a
> > > > > > > 	member template.
> > > > > > > 
> > > > > > > gcc/testsuite/ChangeLog:
> > > > > > > 
> > > > > > > 	* g++.dg/cpp2a/concepts-explicit-spec7.C: New test.
> > > > > > > ---
> > > > > > >     gcc/cp/constraint.cc                          | 18 ++++++++---
> > > > > > >     .../g++.dg/cpp2a/concepts-explicit-spec7.C    | 31
> > > > > > > +++++++++++++++++++
> > > > > > >     2 files changed, 44 insertions(+), 5 deletions(-)
> > > > > > >     create mode 100644
> > > > > > > gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> > > > > > > 
> > > > > > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > > > > > > index ab0f66b3d7e..f1df84c2a1c 100644
> > > > > > > --- a/gcc/cp/constraint.cc
> > > > > > > +++ b/gcc/cp/constraint.cc
> > > > > > > @@ -973,11 +973,19 @@ get_normalized_constraints_from_decl (tree d,
> > > > > > > bool
> > > > > > > diag = false)
> > > > > > >          accepting the latter causes the template parameter level of
> > > > > > > U
> > > > > > >          to be reduced in a way that makes it overly difficult
> > > > > > > substitute
> > > > > > >          concrete arguments (i.e., eventually {int, int} during
> > > > > > > satisfaction.
> > > > > > > */
> > > > > > > -  if (tmpl)
> > > > > > > -  {
> > > > > > > -    if (DECL_LANG_SPECIFIC(tmpl) && !DECL_TEMPLATE_SPECIALIZATION
> > > > > > > (tmpl))
> > > > > > > -      tmpl = most_general_template (tmpl);
> > > > > > > -  }
> > > > > > > +  if (tmpl && DECL_LANG_SPECIFIC (tmpl)
> > > > > > > +      && (!DECL_TEMPLATE_SPECIALIZATION (tmpl)
> > > > > > > +	  /* DECL_TEMPLATE_SPECIALIZATION means we're dealing with
> > > > > > > either a
> > > > > > > +	     partial specialization or an explicit specialization of a
> > > > > > > member
> > > > > > > +	     template.  In the former case all is well: the
> > > > > > > constraints are in
> > > > > > > +	     terms in TMPL's parameters.  But in the latter case
> > > > > > > TMPL's
> > > > > > > +	     parameters are partially instantiated whereas its
> > > > > > > constraints
> > > > > > > +	     aren't, so we need to consider (the parameters of) the
> > > > > > > most
> > > > > > > +	     general template.  The following test distinguishes
> > > > > > > between a
> > > > > > > +	     partial specialization and such an explicit
> > > > > > > specialization.  */
> > > > > > > +	  || (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (tmpl))
> > > > > > > +	      < TMPL_ARGS_DEPTH (DECL_TI_ARGS (tmpl)))))
> > > > > > > +    tmpl = most_general_template (tmpl);
> > > > > > >         d = tmpl ? tmpl : decl;
> > > > > > >     diff --git
> > > > > > > a/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> > > > > > > b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> > > > > > > new file mode 100644
> > > > > > > index 00000000000..5b5a6df20ff
> > > > > > > --- /dev/null
> > > > > > > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
> > > > > > > @@ -0,0 +1,31 @@
> > > > > > > +// PR c++/107522
> > > > > > > +// { dg-do compile { target c++20 } }
> > > > > > > +
> > > > > > > +template<class T>
> > > > > > > +struct A
> > > > > > > +{
> > > > > > > +  template<int N>
> > > > > > > +  static void f() requires (N == 42);
> > > > > > > +
> > > > > > > +  template<class U>
> > > > > > > +  struct B {
> > > > > > > +    template<int N>
> > > > > > > +    static void g() requires (T(N) == 42);
> > > > > > > +  };
> > > > > > > +};
> > > > > > > +
> > > > > > > +template<>
> > > > > > > +template<int N>
> > > > > > > +void A<int>::f() requires (N == 42) { }
> > > > > > > +
> > > > > > > +template<>
> > > > > > > +template<>
> > > > > > > +template<int N>
> > > > > > > +void A<int>::B<int>::g() requires (int(N) == 42) { }
> > > > > > > +
> > > > > > > +int main() {
> > > > > > > +  A<int>::f<42>();
> > > > > > > +  A<int>::f<43>(); // { dg-error "no match" }
> > > > > > > +  A<int>::B<int>::g<42>();
> > > > > > > +  A<int>::B<int>::g<43>(); // { dg-error "no match" }
> > > > > > > +}
> > > > > > 
> > > > > > 
> > > > > 
> > > > 
> > > > 
> > > 
> > 
> > 
>
diff mbox series

Patch

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index ab0f66b3d7e..f1df84c2a1c 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -973,11 +973,19 @@  get_normalized_constraints_from_decl (tree d, bool diag = false)
      accepting the latter causes the template parameter level of U
      to be reduced in a way that makes it overly difficult substitute
      concrete arguments (i.e., eventually {int, int} during satisfaction.  */
-  if (tmpl)
-  {
-    if (DECL_LANG_SPECIFIC(tmpl) && !DECL_TEMPLATE_SPECIALIZATION (tmpl))
-      tmpl = most_general_template (tmpl);
-  }
+  if (tmpl && DECL_LANG_SPECIFIC (tmpl)
+      && (!DECL_TEMPLATE_SPECIALIZATION (tmpl)
+	  /* DECL_TEMPLATE_SPECIALIZATION means we're dealing with either a
+	     partial specialization or an explicit specialization of a member
+	     template.  In the former case all is well: the constraints are in
+	     terms in TMPL's parameters.  But in the latter case TMPL's
+	     parameters are partially instantiated whereas its constraints
+	     aren't, so we need to consider (the parameters of) the most
+	     general template.  The following test distinguishes between a
+	     partial specialization and such an explicit specialization.  */
+	  || (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (tmpl))
+	      < TMPL_ARGS_DEPTH (DECL_TI_ARGS (tmpl)))))
+    tmpl = most_general_template (tmpl);
 
   d = tmpl ? tmpl : decl;
 
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
new file mode 100644
index 00000000000..5b5a6df20ff
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-explicit-spec7.C
@@ -0,0 +1,31 @@ 
+// PR c++/107522
+// { dg-do compile { target c++20 } }
+
+template<class T>
+struct A
+{
+  template<int N>
+  static void f() requires (N == 42);
+
+  template<class U>
+  struct B {
+    template<int N>
+    static void g() requires (T(N) == 42);
+  };
+};
+
+template<>
+template<int N>
+void A<int>::f() requires (N == 42) { }
+
+template<>
+template<>
+template<int N>
+void A<int>::B<int>::g() requires (int(N) == 42) { }
+
+int main() {
+  A<int>::f<42>();
+  A<int>::f<43>(); // { dg-error "no match" }
+  A<int>::B<int>::g<42>();
+  A<int>::B<int>::g<43>(); // { dg-error "no match" }
+}