From: Patrick Palka Date: Thu, 12 Feb 2026 23:20:36 +0000 (-0500) Subject: c++: constrained auto NTTP vs associated constraints X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=12f461009b0e833e4a0404ab348c0fe5b7bbfc75;p=thirdparty%2Fgcc.git c++: constrained auto NTTP vs associated constraints According to [temp.param], the constraint on an auto NTTP is an associated constraint and so should be checked as part of satisfaction of the overall associated constraints, but we currently don't include them in the template's associated constraints and instead check them separately during template argument coercion/deduction. Fixing this is mostly a matter of storing the NTTP's constraint inside TEMPLATE_PARM_CONSTRAINTS instead of PLACEHOLDER_TYPE_CONSTRAINTS and generalizing the relevant template parameter processing subroutines to also handle such NTTPs. While this is straightfoward for "simple" constrained autos, it was later noticed that for e.g. 'C auto* P' or 'D auto& Q' it's not clear how to express their constraint as an associated constraint. For P an option would be C, but for Q it's not clear how to pass the referenced type to C. C would be wrong because we don't want to decay function/array types. So this patch sidesteps this question by preserving the existing behavior for such "non-simple" constrained auto (i.e. don't add them to the associated constraints, and continue ad-hoc checking them during do_auto_deduction). The simple case is by far the most common anyway. The main observeable difference with this change is that such constrained auto NTTPs are now involved in the "more constrained" determination during partial ordering. gcc/cp/ChangeLog: * constraint.cc (finish_shorthand_constraint): Add is_non_type parameter. Handle constrained auto NTTPs. * cp-tree.h (copy_template_args): Declare. (expand_template_argument_pack): Declare. (finish_shorthand_constraint): Adjust declaration. * mangle.cc (write_template_param_decl): Obtain constraints of an auto NTTP through TEMPLATE_PARM_CONSTRAINTS instead of PLACEHOLDER_TYPE_CONSTRAINTS. * parser.cc (cp_parser_constrained_type_template_parm): Inline into its only caller and remove. (cp_parser_constrained_non_type_template_parm): Likewise. (finish_constrained_parameter): Simplify after the above. Replace the type of an ordinary constrained auto NTTP with a non-constrained one and set TEMPLATE_PARM_CONSTRAINTS for it. (cp_parser_template_parameter): Dispatch to finish_constrained_parameter for a constrained auto NTTP. * pt.cc (process_template_parm): Pass is_non_type to finish_shorthand_constraint. Use TEMPLATE_PARM_CONSTRAINTS instead of TREE_TYPE for clarity. (expand_template_argument_pack): Remove forward declaration. (copy_template_args): Likewise. (make_constrained_placeholder_type): Return the type not the TYPE_NAME for consistency with make_auto_1 etc. (do_auto_deduction): Assert we no longer see simple constrained autos during coercion/deduction. gcc/testsuite/ChangeLog: * g++.dg/cpp26/pack-indexing15.C: Adjust expected error upon constrained auto NTTP satisfaction failure. * g++.dg/cpp2a/concepts-placeholder12.C: Likewise. * g++.dg/cpp2a/concepts-pr97093.C: Likewise. * g++.dg/cpp2a/concepts-template-parm2.C: Likewise. * g++.dg/cpp2a/concepts-template-parm6.C: Likewise. * g++.dg/cpp2a/concepts-template-parm12.C: New test. Reviewed-by: Jason Merrill --- diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc index c0f75a01b07..71898271599 100644 --- a/gcc/cp/constraint.cc +++ b/gcc/cp/constraint.cc @@ -1218,7 +1218,7 @@ build_constrained_parameter (tree cnc, tree proto, tree args) done only after the requires clause has been parsed (or not). */ tree -finish_shorthand_constraint (tree decl, tree constr) +finish_shorthand_constraint (tree decl, tree constr, bool is_non_type) { /* No requirements means no constraints. */ if (!constr) @@ -1227,9 +1227,26 @@ finish_shorthand_constraint (tree decl, tree constr) if (error_operand_p (constr)) return NULL_TREE; - tree proto = CONSTRAINED_PARM_PROTOTYPE (constr); - tree con = CONSTRAINED_PARM_CONCEPT (constr); - tree args = CONSTRAINED_PARM_EXTRA_ARGS (constr); + tree proto, con, args; + if (is_non_type) + { + /* This function should not see constrained auto&, auto* NTTPs, and a + simple constrained auto NTTP type should by now have been replaced + by ordinary auto; see finish_constrained_parameter. */ + gcc_checking_assert (is_auto (TREE_TYPE (decl)) + && !is_constrained_auto (TREE_TYPE (decl))); + gcc_checking_assert (TREE_CODE (constr) == TEMPLATE_ID_EXPR); + tree tmpl = TREE_OPERAND (constr, 0); + proto = concept_prototype_parameter (tmpl); + con = DECL_TEMPLATE_RESULT (tmpl); + args = TREE_OPERAND (constr, 1); + } + else + { + proto = CONSTRAINED_PARM_PROTOTYPE (constr); + con = CONSTRAINED_PARM_CONCEPT (constr); + args = CONSTRAINED_PARM_EXTRA_ARGS (constr); + } bool variadic_concept_p = template_parameter_pack_p (proto); bool declared_pack_p = template_parameter_pack_p (decl); @@ -1243,7 +1260,19 @@ finish_shorthand_constraint (tree decl, tree constr) /* Build the concept constraint-expression. */ tree tmpl = DECL_TI_TEMPLATE (con); - tree check = build_concept_check (tmpl, arg, args, tf_warning_or_error); + tree check; + if (is_non_type) + { + arg = finish_decltype_type (arg, /*id_expr=*/true, tf_warning_or_error); + if (ARGUMENT_PACK_P (TREE_VEC_ELT (args, 0))) + args = expand_template_argument_pack (args); + else + args = copy_template_args (args); + TREE_VEC_ELT (args, 0) = arg; + check = build_concept_check (tmpl, args, tf_warning_or_error); + } + else + check = build_concept_check (tmpl, arg, args, tf_warning_or_error); /* Make the check a fold-expression if needed. Use UNKNOWN_LOCATION so write_template_args can tell the diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index fcad67a662c..98411317064 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -8113,6 +8113,8 @@ extern bool is_specialization_of_friend (tree, tree); extern bool comp_template_args (tree, tree, tree * = NULL, tree * = NULL); extern int template_args_equal (tree, tree); +extern tree copy_template_args (tree); +extern tree expand_template_argument_pack (tree); extern tree maybe_process_partial_specialization (tree); extern tree most_specialized_instantiation (tree); extern tree most_specialized_partial_spec (tree, tsubst_flags_t, bool = false); @@ -9176,7 +9178,7 @@ extern tree build_concept_check (tree, tree, tree, tsubst_flags_ extern tree build_constrained_parameter (tree, tree, tree = NULL_TREE); extern bool equivalent_placeholder_constraints (tree, tree); extern hashval_t iterative_hash_placeholder_constraint (tree, hashval_t); -extern tree finish_shorthand_constraint (tree, tree); +extern tree finish_shorthand_constraint (tree, tree, bool); extern tree finish_requires_expr (location_t, tree, tree); extern tree finish_simple_requirement (location_t, tree); extern tree finish_type_requirement (location_t, tree); diff --git a/gcc/cp/mangle.cc b/gcc/cp/mangle.cc index de9e960c683..75d8c6badd6 100644 --- a/gcc/cp/mangle.cc +++ b/gcc/cp/mangle.cc @@ -1921,8 +1921,10 @@ write_template_param_decl (tree parm) write_string ("Tn"); tree type = TREE_TYPE (decl); + /* TODO: We need to also mangle constrained auto*, auto&, etc, but + it's not clear how. See finish_constrained_parameter. */ if (tree c = (is_auto (type) - ? PLACEHOLDER_TYPE_CONSTRAINTS (type) + ? TEMPLATE_PARM_CONSTRAINTS (parm) : NULL_TREE)) { if (AUTO_IS_DECLTYPE (type)) diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 0f53ff90215..97f2ddd62b3 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -20576,34 +20576,6 @@ cp_parser_check_constrained_type_parm (cp_parser *parser, return true; } -/* Finish parsing/processing a template type parameter and checking - various restrictions. */ - -static inline tree -cp_parser_constrained_type_template_parm (cp_parser *parser, - tree id, - cp_parameter_declarator* parmdecl) -{ - if (cp_parser_check_constrained_type_parm (parser, parmdecl)) - return finish_template_type_parm (class_type_node, id); - else - return error_mark_node; -} - -/* Create a new non-type template parameter from the given PARM - declarator. */ - -static tree -cp_parser_constrained_non_type_template_parm (bool *is_non_type, - cp_parameter_declarator *parm) -{ - *is_non_type = true; - cp_declarator *decl = parm->declarator; - cp_decl_specifier_seq *specs = &parm->decl_specifiers; - specs->type = TREE_TYPE (DECL_INITIAL (specs->type)); - return grokdeclarator (decl, specs, TPARM, 0, NULL); -} - /* Build a constrained template parameter based on the PARMDECL declarator. The type of PARMDECL is the constrained type, which refers to the prototype template parameter that ultimately @@ -20614,24 +20586,60 @@ finish_constrained_parameter (cp_parser *parser, cp_parameter_declarator *parmdecl, bool *is_non_type) { - tree decl = parmdecl->decl_specifiers.type; + tree constr = parmdecl->decl_specifiers.type; tree id = get_unqualified_id (parmdecl->declarator); tree def = parmdecl->default_argument; - tree proto = DECL_INITIAL (decl); /* Build the parameter. Return an error if the declarator was invalid. */ + bool set_template_parm_constraints_p = true; tree parm; - if (TREE_CODE (proto) == TYPE_DECL) - parm = cp_parser_constrained_type_template_parm (parser, id, parmdecl); + if (is_constrained_auto (constr)) + { + /* Constrained non-type parameter. */ + *is_non_type = true; + if (!parmdecl->declarator + || parmdecl->declarator->kind == cdk_id) + /* For a simple constrained auto NTTP, move its constraint from + PLACEHOLDER_TYPE_CONSTRAINTS to TEMPLATE_PARM_CONSTRAINTS to + eventually include them in the template's associated constraints. + finish_shorthand_constraint will convert the constraint to its + final form. */ + parmdecl->decl_specifiers.type = (AUTO_IS_DECLTYPE (constr) + ? make_decltype_auto () + : make_auto ()); + else + /* ??? For constrained auto*, auto& etc it's not clear how to represent + the type-constraint as an associated constraint (we need it in terms + of the pointed-to type). We keep it in PLACEHOLDER_TYPE_CONSTRAINTS + and effectively treat it like a non-NTTP constrained auto. */ + set_template_parm_constraints_p = false; + parm = grokdeclarator (parmdecl->declarator, + &parmdecl->decl_specifiers, + TPARM, /*initialized=*/0, /*attrlist=*/NULL); + } else - parm = cp_parser_constrained_non_type_template_parm (is_non_type, parmdecl); + { + /* Constrained type parameter. */ + gcc_checking_assert (CONSTRAINED_PARM_CONCEPT (constr)); + if (cp_parser_check_constrained_type_parm (parser, parmdecl)) + parm = finish_template_type_parm (class_type_node, id); + else + parm = error_mark_node; + } if (parm == error_mark_node) return error_mark_node; /* Finish the parameter decl and create a node attaching the default argument and constraint. */ parm = build_tree_list (def, parm); - TEMPLATE_PARM_CONSTRAINTS (parm) = decl; + if (set_template_parm_constraints_p) + { + if (*is_non_type) + TEMPLATE_PARM_CONSTRAINTS (parm) + = PLACEHOLDER_TYPE_CONSTRAINTS (constr); + else + TEMPLATE_PARM_CONSTRAINTS (parm) = constr; + } return parm; } @@ -20828,7 +20836,9 @@ cp_parser_template_parameter (cp_parser* parser, bool *is_non_type, } /* The parameter may have been constrained type parameter. */ - if (declares_constrained_type_template_parameter (parameter_declarator)) + tree type = parameter_declarator->decl_specifiers.type; + if (declares_constrained_type_template_parameter (parameter_declarator) + || (type && is_constrained_auto (type))) return finish_constrained_parameter (parser, parameter_declarator, is_non_type); diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index 20fd30a8b47..1d11b07d567 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -169,7 +169,6 @@ static tree convert_template_argument (tree, tree, tree, tsubst_flags_t, int, tree); static tree for_each_template_parm (tree, tree_fn_t, void*, hash_set *, bool, tree_fn_t = NULL); -static tree expand_template_argument_pack (tree); static tree build_template_parm_index (int, int, int, tree, tree); static bool inline_needs_template_parms (tree, bool); static void push_inline_template_parms_recursive (tree, int); @@ -184,7 +183,6 @@ static int template_decl_level (tree); static int check_cv_quals_for_unify (int, tree, tree); static int unify_pack_expansion (tree, tree, tree, tree, unification_kind_t, bool, bool); -static tree copy_template_args (tree); static tree tsubst_template_parms (tree, tree, tsubst_flags_t); static void tsubst_each_template_parm_constraints (tree, tree, tsubst_flags_t); static tree tsubst_arg_types (tree, tree, tree, tsubst_flags_t, tree); @@ -4747,7 +4745,7 @@ process_template_parm (tree list, location_t parm_loc, tree parm, tree decl = NULL_TREE; tree defval = TREE_PURPOSE (parm); - tree constr = TREE_TYPE (parm); + tree constr = TEMPLATE_PARM_CONSTRAINTS (parm); if (is_non_type) { @@ -4845,7 +4843,7 @@ process_template_parm (tree list, location_t parm_loc, tree parm, /* Build requirements for the type/template parameter. This must be done after SET_DECL_TEMPLATE_PARM_P or process_template_parm could fail. */ - tree reqs = finish_shorthand_constraint (parm, constr); + tree reqs = finish_shorthand_constraint (parm, constr, is_non_type); decl = pushdecl (decl); if (!is_non_type) @@ -14400,7 +14398,7 @@ make_argument_pack (tree vec) /* Return an exact copy of template args T that can be modified independently. */ -static tree +tree copy_template_args (tree t) { if (t == error_mark_node) @@ -30840,8 +30838,7 @@ make_constrained_placeholder_type (tree type, tree con, tree args) /* Our canonical type depends on the constraint. */ TYPE_CANONICAL (type) = canonical_type_parameter (type); - /* Attach the constraint to the type declaration. */ - return TYPE_NAME (type); + return type; } /* Make a "constrained auto" type-specifier. */ @@ -32657,6 +32654,11 @@ do_auto_deduction (tree type, tree init, tree auto_node, /* Constraints will be checked after deduction. */; else if (tree constr = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node))) { + if (context == adc_unify) + /* Simple constrained auto NTTPs should have gotten their constraint + moved to the template's associated constraints. */ + gcc_checking_assert (type != auto_node); + if (processing_template_decl) { gcc_checking_assert (context == adc_variable_type diff --git a/gcc/testsuite/g++.dg/cpp26/pack-indexing15.C b/gcc/testsuite/g++.dg/cpp26/pack-indexing15.C index 3f8382b12cd..0fd5a6665b1 100644 --- a/gcc/testsuite/g++.dg/cpp26/pack-indexing15.C +++ b/gcc/testsuite/g++.dg/cpp26/pack-indexing15.C @@ -17,4 +17,4 @@ foo () } Set sb; -Set sf; // { dg-error "placeholder constraints not satisfied" } +Set sf; // { dg-error "constraint failure" } diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder12.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder12.C index 22f0ac5e26a..edca8f7199b 100644 --- a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder12.C +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder12.C @@ -22,8 +22,8 @@ int main() { A::g(X<0>{}); // { dg-error "no match|constraints" } bool v1 = A::value<0>; - bool v2 = A::value<0>; // { dg-error "constraints" } + bool v2 = A::value<0>; // { dg-error "invalid variable template" } A::D<0> d1; - A::D<0> d2; // { dg-error "constraints" } + A::D<0> d2; // { dg-error "constraint failure" } } diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-pr97093.C b/gcc/testsuite/g++.dg/cpp2a/concepts-pr97093.C index d662552614e..355f195ac0a 100644 --- a/gcc/testsuite/g++.dg/cpp2a/concepts-pr97093.C +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-pr97093.C @@ -29,4 +29,4 @@ struct pc }; constexpr auto cc = pc {}; -constexpr auto mmcc = m {}; // { dg-error "not satisfied" } +constexpr auto mmcc = m {}; // { dg-error "constraint failure" } diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-template-parm12.C b/gcc/testsuite/g++.dg/cpp2a/concepts-template-parm12.C new file mode 100644 index 00000000000..fc7dd3d0236 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-template-parm12.C @@ -0,0 +1,31 @@ +// { dg-do compile { target c++20 } } +// Verify partial ordering with respect to associated constraints +// works in the presence of constrained NTTPs. + +template concept C = true; + +template concept D = C && true; + +template concept E = true; + +template void f() = delete; +template void f(); // more constrained + +template void g(); +template void g(); // redeclaration + +template void h(); +template void h(); // ambiguous + +template struct A; +template struct A { }; // more constrained + +template struct B; +template struct B { }; // { dg-error "not more constrained" } + +int main() { + f<0>(); + g<0>(); + h<0>(); // { dg-error "ambiguous" } + A<0> a; +} diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-template-parm2.C b/gcc/testsuite/g++.dg/cpp2a/concepts-template-parm2.C index 3bb2f576a87..a9b15dabc0c 100644 --- a/gcc/testsuite/g++.dg/cpp2a/concepts-template-parm2.C +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-template-parm2.C @@ -12,4 +12,4 @@ template struct S1 { }; template struct S2 { }; S1<> s1; // { dg-error "constraint failure" } -S2<> s2; // { dg-error "placeholder constraints not satisfied" } +S2<> s2; // { dg-error "constraint failure" } diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-template-parm6.C b/gcc/testsuite/g++.dg/cpp2a/concepts-template-parm6.C index c7d9964f738..04c2e1c70ba 100644 --- a/gcc/testsuite/g++.dg/cpp2a/concepts-template-parm6.C +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-template-parm6.C @@ -40,5 +40,5 @@ template struct S3 { }; // requires (C && ...) S3 x0; // { dg-error "template constraint failure" } template struct S4 { }; // requires (C && ...) with each X deduced -S4<0, 1, 2, 'a'> x1; // { dg-error "placeholder constraints not satisfied" } +S4<0, 1, 2, 'a'> x1; // { dg-error "template constraint failure" }