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. 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.) 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. 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 | 98 +++++++++++-------- .../g++.dg/cpp2a/concepts-partial-spec9.C | 24 +++++ 2 files changed, 79 insertions(+), 43 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,52 +29620,63 @@ 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) - /* In general we can't check satisfaction until we know all - template arguments. */ + 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) + { + /* 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. */ + 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 - && DECL_TEMPLATE_INFO (current_function_decl)) - outer_targs = DECL_TI_ARGS (current_function_decl); + if ((context == adc_return_type || context == adc_variable_type) + && current_function_decl + && DECL_TEMPLATE_INFO (current_function_decl)) + outer_targs = DECL_TI_ARGS (current_function_decl); - tree full_targs = add_to_template_args (outer_targs, targs); - if (!constraints_satisfied_p (auto_node, full_targs)) - { - if (complain & tf_warning_or_error) - { - auto_diagnostic_group d; - switch (context) - { - case adc_unspecified: - case adc_unify: - error("placeholder constraints not satisfied"); - break; - case adc_variable_type: - case adc_decomp_type: - error ("deduced initializer does not satisfy " - "placeholder constraints"); - break; - case adc_return_type: - error ("deduced return type does not satisfy " - "placeholder constraints"); - break; - case adc_requirement: - error ("deduced expression type does not satisfy " - "placeholder constraints"); - break; - } - diagnose_constraints (input_location, auto_node, full_targs); - } - return error_mark_node; - } - } + tree full_targs = add_to_template_args (outer_targs, targs); + if (!constraints_satisfied_p (auto_node, full_targs)) + { + if (complain & tf_warning_or_error) + { + auto_diagnostic_group d; + switch (context) + { + case adc_unspecified: + case adc_unify: + error("placeholder constraints not satisfied"); + break; + case adc_variable_type: + case adc_decomp_type: + error ("deduced initializer does not satisfy " + "placeholder constraints"); + break; + case adc_return_type: + error ("deduced return type does not satisfy " + "placeholder constraints"); + break; + case adc_requirement: + error ("deduced expression type does not satisfy " + "placeholder constraints"); + break; + } + diagnose_constraints (input_location, auto_node, full_targs); + } + return error_mark_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); */ -- 2.31.0.rc0.75.gec125d1bc1