https://gcc.gnu.org/g:12f461009b0e833e4a0404ab348c0fe5b7bbfc75

commit r16-7491-g12f461009b0e833e4a0404ab348c0fe5b7bbfc75
Author: Patrick Palka <[email protected]>
Date:   Thu Feb 12 18:20:36 2026 -0500

    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<decltype(*P)>, but for Q it's not clear how to pass
    the referenced type to C.  C<decltype(auto(Q))> 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 <[email protected]>

Diff:
---
 gcc/cp/constraint.cc                               | 39 +++++++++--
 gcc/cp/cp-tree.h                                   |  4 +-
 gcc/cp/mangle.cc                                   |  4 +-
 gcc/cp/parser.cc                                   | 80 ++++++++++++----------
 gcc/cp/pt.cc                                       | 16 +++--
 gcc/testsuite/g++.dg/cpp26/pack-indexing15.C       |  2 +-
 .../g++.dg/cpp2a/concepts-placeholder12.C          |  4 +-
 gcc/testsuite/g++.dg/cpp2a/concepts-pr97093.C      |  2 +-
 .../g++.dg/cpp2a/concepts-template-parm12.C        | 31 +++++++++
 .../g++.dg/cpp2a/concepts-template-parm2.C         |  2 +-
 .../g++.dg/cpp2a/concepts-template-parm6.C         |  2 +-
 11 files changed, 131 insertions(+), 55 deletions(-)

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index c0f75a01b076..718982715990 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 fcad67a662cc..984113170640 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 de9e960c683c..75d8c6badd66 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 0f53ff902159..97f2ddd62b31 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 20fd30a8b473..1d11b07d567d 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<tree> *, 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 3f8382b12cd2..0fd5a6665b1f 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<bool> sb;
-Set<float> sf; // { dg-error "placeholder constraints not satisfied" }
+Set<float> 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 22f0ac5e26a1..edca8f7199bc 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<false>::g(X<0>{}); // { dg-error "no match|constraints" }
 
   bool v1 = A<true>::value<0>;
-  bool v2 = A<false>::value<0>;  // { dg-error "constraints" }
+  bool v2 = A<false>::value<0>;  // { dg-error "invalid variable template" }
 
   A<true>::D<0> d1;
-  A<false>::D<0> d2; // { dg-error "constraints" }
+  A<false>::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 d662552614e5..355f195ac0ad 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 <cc> {}; // { dg-error "not satisfied" }
+constexpr auto mmcc = m <cc> {}; // { 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 000000000000..fc7dd3d02369
--- /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<class T> concept C = true;
+
+template<class T> concept D = C<T> && true;
+
+template<class T> concept E = true;
+
+template<C auto V> void f() = delete;
+template<D auto V> void f(); // more constrained
+
+template<C auto V> void g();
+template<C auto V> void g(); // redeclaration
+
+template<C auto V> void h();
+template<E auto V> void h(); // ambiguous
+
+template<C auto V> struct A;
+template<D auto V> struct A<V> { }; // more constrained
+
+template<D auto V> struct B;
+template<C auto V> struct B<V> { }; // { 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 3bb2f576a873..a9b15dabc0cf 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<Int T = char> struct S1 { };
 template<Int auto X = false> 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 c7d9964f7388..04c2e1c70ba6 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<Int... Ts> struct S3 { }; // requires (C<Ts> && ...)
 S3<int, int, char> x0; // { dg-error "template constraint failure" }
 
 template<Int auto... Xs> struct S4 { }; // requires (C<X> && ...) 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" }

Reply via email to