On Fri, 5 Mar 2021, Jason Merrill wrote: > On 3/4/21 9:55 PM, Patrick Palka wrote: > > On Thu, 4 Mar 2021, Patrick Palka wrote: > > > > > 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. > > > > Here's a patch that incorporates these two ideas: > > > > -- >8 -- > > > > Subject: [PATCH] c++: adc_unify deduction with constrained auto [PR99365] > > > > 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 the 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. > > > > We additionally now need to pass the 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 when the caller has already done so, so this patch adds > > a TEMPLATE_TYPE_LEVEL check to do_auto_deduction to that effect. > > > > Overall, these changes fix partial specialization 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 > > properly is perhaps too risky at this stage, so this patch adds a hack > > to do_auto_deduction to compensate for callers that don't supply all > > outer template arguments. The goal of this hack is to ensure > > placeholder type constraint checking continues to work whenever it > > worked before r11-7454, namely whenever the constraint is non-dependent. > > > > Finally, this patch allows do_auto_deduction to resolve a constrained > > placeholder type ahead of time (at template parse time), as long as the > > constraint is non-dependent. > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for > > trunk? Also tested on range-v3 and cmcstl2. > > > > gcc/cp/ChangeLog: > > > > 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. Otherwise don't > > punt unless any of the explicit arguments in the constraint are > > dependent. Add some clarifying sanity checks. Add a hack to > > add missing outermost template levels to outer_args before > > checking satisfaction. Don't substitute outer_targs into type > > if it's already been done. > > > > gcc/testsuite/ChangeLog: > > > > PR c++/99365 > > * g++.dg/cpp2a/concepts-partial-spec9.C: New test. > > * g++.dg/cpp2a/concepts-placeholder4: New test. > > --- > > gcc/cp/pt.c | 44 ++++++++++++++++--- > > .../g++.dg/cpp2a/concepts-partial-spec9.C | 23 ++++++++++ > > .../g++.dg/cpp2a/concepts-placeholder4.C | 24 ++++++++++ > > 3 files changed, 85 insertions(+), 6 deletions(-) > > create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C > > create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C > > > > diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c > > index 83589101c0d..31386a74a59 100644 > > --- a/gcc/cp/pt.c > > +++ b/gcc/cp/pt.c > > @@ -23681,7 +23681,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; > > } > > @@ -29611,13 +29612,24 @@ 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. */; > > Let's say "constraints will be checked after deduction".
Done. > > > + else if (tree constr = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS > > (auto_node))) > > { > > if (processing_template_decl) > > - /* In general we can't check satisfaction until we know all > > - template arguments. */ > > + { > > + gcc_checking_assert (context == adc_variable_type > > + || context == adc_return_type); > > + gcc_checking_assert (!type_dependent_expression_p (init)); > > This assert seems redundant with the condition at the top of the function, > though I suppose it's harmless to have it here as well. Yeah, I figured the assert might be beneficial to the reader, as a reminder that we're dealing with a non-dependent initializer, since the condition is a bit far away at this point. > > > + /* Check if any of the explicit arguments in the constraint > > + are dependent. If so, we need to wait until instantiation time > > + to resolve the constriant. */ > > + tree cid = unpack_concept_check (constr); > > + tree cargs = TREE_OPERAND (cid, 1); > > + for (int i = 1; i < TREE_VEC_LENGTH (cargs); ++i) > > + if (dependent_template_arg_p (TREE_VEC_ELT (cargs, i))) > > return type; > > Let's factor this out; I imagine testing whether a concept-check is dependent > (apart from the placeholder type itself) may be more broadly useful. Done as well (into a predicate placeholder_type_constraint_dependent_p). How does the following look? (testing in progress) -- >8 -- gcc/cp/ChangeLog: PR c++/99365 * pt.c (unify) <case TEMPLATE_TYPE_PARM>: Pass targs as outer_targs to do_auto_deduction. (placeholder_type_constraint_dependent_p): Define. (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. Otherwise don't punt unless placeholder_type_constraint_dependent_p holds. Add some clarifying sanity checks. Add a hack to add missing outermost template levels to outer_args before checking satisfaction. Don't substitute outer_targs into type if it's already been done. gcc/testsuite/ChangeLog: PR c++/99365 * g++.dg/cpp2a/concepts-partial-spec9.C: New test. * g++.dg/cpp2a/concepts-placeholder4: New test. --- gcc/cp/pt.c | 57 +++++++++++++++++-- .../g++.dg/cpp2a/concepts-partial-spec9.C | 23 ++++++++ .../g++.dg/cpp2a/concepts-placeholder4.C | 24 ++++++++ 3 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index 8ca3dc8ec2b..b2ecd0b3213 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -23683,7 +23683,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; } @@ -28205,6 +28206,23 @@ make_constrained_decltype_auto (tree con, tree args) return make_constrained_placeholder_type (type, con, args); } +/* Return true if the placeholder type constraint T has any dependent + (explicit) template arguments. */ + +static bool +placeholder_type_constraint_dependent_p (tree t) +{ + tree id = unpack_concept_check (t); + tree args = TREE_OPERAND (id, 1); + tree first = TREE_VEC_ELT (args, 0); + gcc_checking_assert (TREE_CODE (first) == WILDCARD_DECL + || is_auto (first)); + for (int i = 1; i < TREE_VEC_LENGTH (args); ++i) + if (dependent_template_arg_p (TREE_VEC_ELT (args, i))) + return true; + return false; +} + /* Build and return a concept definition. Like other templates, the CONCEPT_DECL node is wrapped by a TEMPLATE_DECL. This returns the the TEMPLATE_DECL. */ @@ -29613,13 +29631,20 @@ 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) + /* Constraints will be checked after deduction. */; + else if (tree constr = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node))) { if (processing_template_decl) - /* In general we can't check satisfaction until we know all - template arguments. */ + { + gcc_checking_assert (context == adc_variable_type + || context == adc_return_type); + gcc_checking_assert (!type_dependent_expression_p (init)); + /* If the constraint is dependent, we need to wait until + instantiation time to resolve the placeholder. */ + if (placeholder_type_constraint_dependent_p (constr)) return type; + } if ((context == adc_return_type || context == adc_variable_type) && current_function_decl @@ -29627,6 +29652,23 @@ do_auto_deduction (tree type, tree init, tree auto_node, outer_targs = DECL_TI_ARGS (current_function_decl); tree full_targs = add_to_template_args (outer_targs, targs); + + /* HACK: Compensate for callers not always communicating all levels of + outer template arguments by filling in the outermost missing levels + with dummy levels before checking satisfaction. We'll still crash + if the constraint depends on a template argument belonging to one of + these missing levels, but this hack otherwise allows us to handle a + large subset of possible constraints (including all non-dependent + constraints). */ + if (int missing_levels = (TEMPLATE_TYPE_ORIG_LEVEL (auto_node) + - TMPL_ARGS_DEPTH (full_targs))) + { + tree dummy_levels = make_tree_vec (missing_levels); + for (int i = 0; i < missing_levels; ++i) + TREE_VEC_ELT (dummy_levels, i) = make_tree_vec (0); + full_targs = add_to_template_args (dummy_levels, full_targs); + } + if (!constraints_satisfied_p (auto_node, full_targs)) { if (complain & tf_warning_or_error) @@ -29658,7 +29700,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..3dae24d915a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C @@ -0,0 +1,23 @@ +// 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); + +template <class> struct O { + 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(O<void>::A<int, 0>::i == 1); +static_assert(O<void>::A<char, 0>::i == 0); +static_assert(O<void>::A<int, '0'>::i == 0); +static_assert(O<void>::A<char, '0'>::i == 1); diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C new file mode 100644 index 00000000000..082c5d36cbe --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C @@ -0,0 +1,24 @@ +// { dg-do compile { target c++20 } } + +template <class T, class U> concept same_as = __is_same(T, U); + +// non-dependent initializer, always-satisfied constraint resolved at parse time +template <class T> same_as<bool> auto x = true; + +template <auto V> same_as<int> auto y = V; // { dg-error "constraint" } + +template <class> +struct A { + template <auto V> static inline same_as<int> auto z = V; // { dg-error "constraint" } +}; + +int main() { + x<int>; // { dg-bogus "" } + y<0>; // { dg-bogus "" } + y<'0'>; // { dg-message "required from here" } + A<void>::z<0>; // { dg-bogus "" } + A<void>::z<'0'>; // { dg-message "required from here" } +} + +// non-dependent initializer, never-satisfied constraint diagnosed at parse time +template <class T> same_as<int> auto z = true; // { dg-error "constraint" } -- 2.31.0.rc0.75.gec125d1bc1