Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look
OK for trunk?  This is an update of the patch
https://gcc.gnu.org/pipermail/gcc-patches/2025-March/679588.html
that sidesteps the auto*/auto& issue.

-- >8 --

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 treat
them as part of the template's associated constraints and instead
check them separately during template argument coercion/deduction.

Fixing this is mostly a matter of storing the constrained auto's
type-constraint inside TEMPLATE_PARM_CONSTRAINTS instead of
PLACEHOLDER_TYPE_CONSTRAINTS and teaching the relevant subroutines
to recognize constrained auto NTTPs (alongside constrained type
parameters).

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 at
all how to express their type-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 not be
right because it'd wrongly decay function/array types.

So this patch sidesteps this question by preserving the existing
behavior for "non-simple" constrained auto (i.e. don't add them to the
associated constraints, and continue 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.
        (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.
        (copy_template_args): Remove static.
        (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/cpp2a/concepts-placeholder12.C: Adjust expected error
        upon constrained auto NTTP satisfaction failure.
        * 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.
---
 gcc/cp/constraint.cc                          | 36 +++++++--
 gcc/cp/cp-tree.h                              |  3 +-
 gcc/cp/mangle.cc                              |  4 +-
 gcc/cp/parser.cc                              | 80 +++++++++++--------
 gcc/cp/pt.cc                                  | 15 ++--
 .../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 +-
 10 files changed, 126 insertions(+), 53 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-template-parm12.C

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 0b91e8785631..a48c6ea770f0 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 have been replaced by
+        ordinary auto at this point; 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,16 @@ 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);
+      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 59ab4e40430d..b784dd45a595 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -8112,6 +8112,7 @@ 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 maybe_process_partial_specialization (tree);
 extern tree most_specialized_instantiation     (tree);
 extern tree most_specialized_partial_spec       (tree, tsubst_flags_t, bool = 
false);
@@ -9175,7 +9176,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 4d988c27cb80..5ecaa1cb5e6d 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -20560,34 +20560,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
@@ -20598,24 +20570,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).  So we keep it in TEMPLATE_PARM_CONSTRAINTS
+          and effectively treat it like a non-template 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;
 }
@@ -20812,7 +20820,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 de101b180e38..d43ac7503b2d 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -184,7 +184,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 +4746,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 +4844,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)
@@ -14395,7 +14394,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)
@@ -30822,8 +30821,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.  */
@@ -32638,6 +32636,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 constraints
+          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/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..713b1d050825
--- /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(); // neither more nor less constrained
+
+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" }
 
-- 
2.53.0.rc1.65.gea24e2c554

Reply via email to