]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: constrained auto NTTP vs associated constraints
authorPatrick Palka <ppalka@redhat.com>
Thu, 12 Feb 2026 23:20:36 +0000 (18:20 -0500)
committerPatrick Palka <ppalka@redhat.com>
Thu, 12 Feb 2026 23:20:36 +0000 (18:20 -0500)
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 <jason@redhat.com>
gcc/cp/constraint.cc
gcc/cp/cp-tree.h
gcc/cp/mangle.cc
gcc/cp/parser.cc
gcc/cp/pt.cc
gcc/testsuite/g++.dg/cpp26/pack-indexing15.C
gcc/testsuite/g++.dg/cpp2a/concepts-placeholder12.C
gcc/testsuite/g++.dg/cpp2a/concepts-pr97093.C
gcc/testsuite/g++.dg/cpp2a/concepts-template-parm12.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/concepts-template-parm2.C
gcc/testsuite/g++.dg/cpp2a/concepts-template-parm6.C

index c0f75a01b076b709a368109c96c118b66b475459..718982715990e1185d584e15328597dfbb7ce779 100644 (file)
@@ -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
index fcad67a662ccabead08a8af8609a70113a6af002..9841131706405a4d866de1e6bfacf30ec839dd48 100644 (file)
@@ -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);
index de9e960c683c0e4de4beb73726cc12c0d5b8f5d8..75d8c6badd66416b57913285cee40822a4f12f2d 100644 (file)
@@ -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))
index 0f53ff902159829ccdea4fc525f40578c8efb68e..97f2ddd62b31b3a9c92fad84597e35e01d48dce1 100644 (file)
@@ -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);
index 20fd30a8b473447ef65f22b1580ebd14e037140c..1d11b07d567dcb39d7e9a65c844887cab6bfaa9f 100644 (file)
@@ -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
index 3f8382b12cd27c5aba0755d3e76ce2f7646a4bb5..0fd5a6665b1f9ad11fa7bfffeb8b088cc857af90 100644 (file)
@@ -17,4 +17,4 @@ foo ()
 }
 
 Set<bool> sb;
-Set<float> sf; // { dg-error "placeholder constraints not satisfied" }
+Set<float> sf; // { dg-error "constraint failure" }
index 22f0ac5e26a150c984baee82ab81d8726612f586..edca8f7199bc2afd10b5dd3bf92f2406c33db35d 100644 (file)
@@ -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" }
 }
index d662552614e5216388059f3922a5ccc5c0517917..355f195ac0ad77ef6ba661525cce0d2a93caba63 100644 (file)
@@ -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 (file)
index 0000000..fc7dd3d
--- /dev/null
@@ -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;
+}
index 3bb2f576a873d645a4e65db97c827b1ef7714a53..a9b15dabc0cfe58f87e0226bf85fe76730d18be3 100644 (file)
@@ -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" }
index c7d9964f7388bc6c5042463d9d3693d0a7c2858e..04c2e1c70ba639ff3a8440975a18aeb90c549880 100644 (file)
@@ -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" }