On Thu, 4 Mar 2021, Patrick Palka wrote:

> On Thu, 4 Mar 2021, Jason Merrill wrote:
> 
> > On 3/4/21 11:32 AM, Patrick Palka wrote:
> > > On Thu, 4 Mar 2021, Patrick Palka wrote:
> > > 
> > > > My recent r11-7454 changed the way do_auto_deduction handles constrained
> > > > placeholders during template argument deduction (context == adc_unify)
> > > > when processing_template_decl != 0.
> > > > 
> > > > Before the patch, when processing_template_decl != 0 we would just
> > > > ignore the constraints on the placeholder in this situation, and proceed
> > > > with deduction:
> > > > 
> > > >    /* Check any placeholder constraints against the deduced type. */
> > > >    if (flag_concepts && !processing_template_decl)
> > > >      if (tree check = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS
> > > > (auto_node)))
> > > >        {
> > > >          ...
> > > > 
> > > > After the patch, we now punt and return the original placeholder type:
> > > > 
> > > >    /* Check any placeholder constraints against the deduced type. */
> > > >    if (flag_concepts)
> > > >      if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > > >        {
> > > >          if (processing_template_decl)
> > > >            /* In general we can't check satisfaction until we know all
> > > >               template arguments.  */
> > > >            return type;
> > > >          ...
> > > > 
> > > > While this change fixed instances where we'd prematurely resolve a
> > > > constrained placeholder return or variable type with non-dependent
> > > > initializer at template parse time (such as PR96444), it broke the
> > > > adc_unify callers that rely on this previous behavior.
> > > > 
> > > > So this patch restores the previous behavior during adc_unify deduction
> > > > while retaining the new behavior only during adc_variable_type or
> > > > adc_return_type deduction.
> > 
> > Sure, it makes sense for adc_unify to behave differently, since in deduction
> > context constraints are checked after all template args have been deduced.
> 
> I see.
> 
> > 
> > > > We additionally now need to pass outer template arguments to
> > > > do_auto_deduction during unify, for sake of constraint checking.
> > > > But we don't want do_auto_deduction to substitute these outer arguments
> > > > into type if it's already been done, hence the added TEMPLATE_TYPE_LEVEL
> > > > check.
> > > > 
> > > > This fixes partial specializations of non-nested templates with
> > > > constrained 'auto' template parameters, but nested templates are still
> > > > broken, ultimately because most_specialized_partial_spec passes only the
> > > > innermost template arguments to get_partial_spec_bindings, and so
> > > > outer_targs during do_auto_deduction (called from unify) contains only
> > > > the innermost template arguments which makes satisfaction unhappy.
> > > > Fixing this might be too invasive at this stage, perhaps..  (Seems we
> > > > need to make most_specialized_partial_spec pass all template arguments
> > > > to get_partial_spec_bindings.)
> > 
> > How did this work before?
> 
> Before, it would work, but only if the constraint didn't also depend on
> any outer template arguments.  do_auto_deduction would just "surgically"
> replace the auto in the concept-id with the type we deduced and leave
> the other template arguments of the concept-id alone.  So if the
> constraint was non-dependent, satisfaction would work regardless of the
> template nesting level.
> 
> Now that we try to do perform satisfaction properly, we're sensitive to
> the template nesting level even if the constraint is otherwise
> non-dependent, because the template nesting level determines the level
> of the auto that appears inside the constraint.  So we rely on
> outer_targs to contain all levels of outer template arguments, because
> we tack on another level to the end of outer_targs which needs to
> map to the level of the auto for satisfaction.
> 
> (As a hacky workaround, when outer_targs is incomplete, can probably
> just augment it with empty levels until it's TEMPLATE_TYPE_LEVEL(auto_node)-1
> levels deep, which would fix the nested template case as long as the
> constraint was depended only on the innermost level of template
> arguments.)
> 
> > 
> > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> > > > trunk?  Also tested on range-v3 and cmcstl2.
> > > 
> > > Here's the same patch generated with -w which hides the noisy indentation
> > > changes:
> > > 
> > > -- >8 --
> > > 
> > >   PR c++/99365
> > >   * pt.c (do_auto_deduction): When processing_template_decl != 0
> > >   and context is adc_unify and we have constraints, pretend the
> > >   constraints are satisfied instead of punting.  Add some
> > >   clarifying sanity checks.  Don't substitute outer_targs into
> > >   type if not needed.
> > > 
> > > gcc/testsuite/ChangeLog:
> > > 
> > >   PR c++/99365
> > >   * g++.dg/cpp2a/concepts-partial-spec9.C: New test.
> > > ---
> > >   gcc/cp/pt.c                                   | 24 ++++++++++++++-----
> > >   .../g++.dg/cpp2a/concepts-partial-spec9.C     | 24 +++++++++++++++++++
> > >   2 files changed, 42 insertions(+), 6 deletions(-)
> > >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > > 
> > > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> > > index a4686e0affb..ce537e4529a 100644
> > > --- a/gcc/cp/pt.c
> > > +++ b/gcc/cp/pt.c
> > > @@ -23693,7 +23693,8 @@ unify (tree tparms, tree targs, tree parm, tree 
> > > arg,
> > > int strict,
> > >             if (tree a = type_uses_auto (tparm))
> > >               {
> > > -       tparm = do_auto_deduction (tparm, arg, a, complain, adc_unify);
> > > +       tparm = do_auto_deduction (tparm, arg, a,
> > > +                                  complain, adc_unify, targs);
> > >                 if (tparm == error_mark_node)
> > >                   return 1;
> > >               }
> > > @@ -29619,13 +29620,21 @@ do_auto_deduction (tree type, tree init, tree
> > > auto_node,
> > >       }
> > >       /* Check any placeholder constraints against the deduced type. */
> > > -  if (flag_concepts)
> > > -    if (NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > > +  if (processing_template_decl && context == adc_unify)
> > > +    /* Pretend constraints are satisfied.  */;
> > > +  else if (flag_concepts
> > > +    && NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
> > >       {
> > >         if (processing_template_decl)
> > > -   /* In general we can't check satisfaction until we know all
> > > -      template arguments.  */
> > > + {
> > > +   /* Even though the initializer is non-dependent, we need to wait
> > > until
> > > +      instantiation time to resolve this constrained placeholder
> > > variable
> > > +      or return type, since the constraint itself may be dependent.  */
> > 
> > Can't we check whether the constraint is dependent, and check satisfaction 
> > if
> > it isn't?  That might be necessary to make later expressions non-dependent
> > that are supposed to be.
> 
> We'd have to check if outer_targs (and the deduced type) is dependent
> too.  But it seems tricky because outer_targs, during adc_unify
> deduction, is usually at least partially empty which is enough to make
> any_dependent_template_arguments_p return true.

Ah sorry, I just realized you probably meant we should check whether the
constraint is dependent during adc_variable_type and adc_return_type
deduction.  I think that might be straightforward; I'll try it.  I'll
also experiment with the hacky workaround mentioned earlier.

> 
> > 
> > > +   gcc_checking_assert (context == adc_variable_type
> > > +                        || context == adc_return_type);
> > > +   gcc_checking_assert (!type_dependent_expression_p (init));
> > >             return type;
> > > + }
> > >           if ((context == adc_return_type || context == adc_variable_type)
> > >             && current_function_decl
> > > @@ -29664,7 +29673,10 @@ do_auto_deduction (tree type, tree init, tree
> > > auto_node,
> > >           }
> > >       }
> > >   -  if (context == adc_unify)
> > > +  if (TEMPLATE_TYPE_LEVEL (auto_node) == 1)
> > > +    /* The outer template arguments are already substituted into type
> > > +       (but we still may have used them for constraint checking above).
> > > */;
> > > +  else if (context == adc_unify)
> > >       targs = add_to_template_args (outer_targs, targs);
> > >     else if (processing_template_decl)
> > >       targs = add_to_template_args (current_template_args (), targs);
> > > diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > > b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > > new file mode 100644
> > > index 00000000000..b79d12b6f17
> > > --- /dev/null
> > > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
> > > @@ -0,0 +1,24 @@
> > > +// PR c++/99365
> > > +// { dg-do compile { target c++20 } }
> > > +
> > > +template <class> concept C = true;
> > > +template <class T, class U> concept D = C<T> && __is_same(T, U);
> > > +
> > > +template <class, C auto> struct A { static const int i = 0; };
> > > +template <class T, D<T> auto V> struct A<T, V> { static const int i = 1; 
> > > };
> > > +
> > > +static_assert(A<int, 0>::i == 1);
> > > +static_assert(A<char, 0>::i == 0);
> > > +static_assert(A<int, '0'>::i == 0);
> > > +static_assert(A<char, '0'>::i == 1);
> > > +
> > > +/* Partial specialization of nested class template with constrained 
> > > 'auto'
> > > +   template parameter still broken:
> > > +
> > > +template <class> struct O {
> > > +  template <C auto> struct B { static const int i = 0; };
> > > +  template <D auto V> struct B<V> { static const int i = 1; };
> > > +};
> > > +
> > > +static_assert(O<void>::B<int, 0>::i == 0);
> > > +static_assert(O<void>::B<int, '0'>::i == 1);  */
> > > 
> > 
> > 
> 

Reply via email to