[gcc r11-7540] c++: adc_unify deduction with constrained auto [PR99365]

Patrick Palka ppalka@gcc.gnu.org
Sat Mar 6 05:08:07 GMT 2021


https://gcc.gnu.org/g:b49d23f3e238c08bdbc5b892b2ed0a57b5f5caf9

commit r11-7540-gb49d23f3e238c08bdbc5b892b2ed0a57b5f5caf9
Author: Patrick Palka <ppalka@redhat.com>
Date:   Sat Mar 6 00:07:35 2021 -0500

    c++: adc_unify deduction with constrained auto [PR99365]
    
    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, we would just
    ignore the constraints on the placeholder, and return the deduced type.
    After the patch, we now punt and return the original placeholder 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 the previous behavior.
    
    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 the outer template arguments to
    do_auto_deduction during unify, for sake of constraint checking.
    But we want to avoid substituting these outer arguments into type
    when the caller has already done so, so this patch adds a
    TEMPLATE_TYPE_LEVEL check to do_auto_deduction to that effect.
    
    This above is enough to fix partial specialization of non-nested
    templates with constrained 'auto' template parameters, but it doesn't
    fix the nested template case, 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, and this breaks satisfaction.  Fixing this properly
    is perhaps too risky at this stage, so this patch adds a hack to
    do_auto_deduction to compensate for callers that don't supply all outer
    template arguments.  The goal of this hack is to ensure placeholder type
    constraint checking continues to work whenever it worked before
    r11-7454, namely whenever the constraint is non-dependent.
    
    Finally, this patch allows do_auto_deduction to resolve a constrained
    placeholder type ahead of time (at template parse time), as long as the
    constraint is non-dependent.
    
    gcc/cp/ChangeLog:
    
            PR c++/99365
            * pt.c (unify) <case TEMPLATE_TYPE_PARM>: Pass targs as
            outer_targs to do_auto_deduction.
            (placeholder_type_constraint_dependent_p): Define.
            (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.  Otherwise don't
            punt unless placeholder_type_constraint_dependent_p holds.
            Add some clarifying sanity checks.  Add a hack to add missing
            outermost template levels to outer_args before checking
            satisfaction.  Don't substitute outer_targs into type if it's
            already been done.
    
    gcc/testsuite/ChangeLog:
    
            PR c++/99365
            * g++.dg/cpp2a/concepts-partial-spec9.C: New test.
            * g++.dg/cpp2a/concepts-placeholder4.C: New test.

Diff:
---
 gcc/cp/pt.c                                        | 133 ++++++++++++++-------
 .../g++.dg/cpp2a/concepts-partial-spec9.C          |  23 ++++
 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C |  24 ++++
 3 files changed, 136 insertions(+), 44 deletions(-)

diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index 1f3cd9c45f1..7794137a359 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -23683,7 +23683,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;
 	    }
@@ -28206,6 +28207,23 @@ make_constrained_decltype_auto (tree con, tree args)
   return make_constrained_placeholder_type (type, con, args);
 }
 
+/* Returns true if the placeholder type constraint T has any dependent
+   (explicit) template arguments.  */
+
+static bool
+placeholder_type_constraint_dependent_p (tree t)
+{
+  tree id = unpack_concept_check (t);
+  tree args = TREE_OPERAND (id, 1);
+  tree first = TREE_VEC_ELT (args, 0);
+  gcc_checking_assert (TREE_CODE (first) == WILDCARD_DECL
+		       || is_auto (first));
+  for (int i = 1; i < TREE_VEC_LENGTH (args); ++i)
+    if (dependent_template_arg_p (TREE_VEC_ELT (args, i)))
+      return true;
+  return false;
+}
+
 /* Build and return a concept definition. Like other templates, the
    CONCEPT_DECL node is wrapped by a TEMPLATE_DECL.  This returns the
    the TEMPLATE_DECL. */
@@ -29614,52 +29632,79 @@ 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.  */
-	  return type;
+  if (processing_template_decl && context == adc_unify)
+    /* Constraints will be checked after deduction.  */;
+  else if (tree constr = NON_ERROR (PLACEHOLDER_TYPE_CONSTRAINTS (auto_node)))
+    {
+      if (processing_template_decl)
+	{
+	  gcc_checking_assert (context == adc_variable_type
+			       || context == adc_return_type);
+	  gcc_checking_assert (!type_dependent_expression_p (init));
+	  /* If the constraint is dependent, we need to wait until
+	     instantiation time to resolve the placeholder.  */
+	  if (placeholder_type_constraint_dependent_p (constr))
+	    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);
+
+      /* HACK: Compensate for callers not always communicating all levels of
+	 outer template arguments by filling in the outermost missing levels
+	 with dummy levels before checking satisfaction.  We'll still crash
+	 if the constraint depends on a template argument belonging to one of
+	 these missing levels, but this hack otherwise allows us to handle a
+	 large subset of possible constraints (including all non-dependent
+	 constraints).  */
+      if (int missing_levels = (TEMPLATE_TYPE_ORIG_LEVEL (auto_node)
+				- TMPL_ARGS_DEPTH (full_targs)))
+	{
+	  tree dummy_levels = make_tree_vec (missing_levels);
+	  for (int i = 0; i < missing_levels; ++i)
+	    TREE_VEC_ELT (dummy_levels, i) = make_tree_vec (0);
+	  full_targs = add_to_template_args (dummy_levels, full_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..3dae24d915a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-partial-spec9.C
@@ -0,0 +1,23 @@
+// 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);
+
+template <class> struct O {
+  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(O<void>::A<int, 0>::i == 1);
+static_assert(O<void>::A<char, 0>::i == 0);
+static_assert(O<void>::A<int, '0'>::i == 0);
+static_assert(O<void>::A<char, '0'>::i == 1);
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
new file mode 100644
index 00000000000..9f39d452840
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder4.C
@@ -0,0 +1,24 @@
+// { dg-do compile { target c++20 } }
+
+template <class T, class U> concept same_as = __is_same(T, U);
+
+// this constrained placeholder type should get resolved at parse time
+template <class T> same_as<bool> auto x = true;
+
+template <auto V> same_as<int> auto y = V; // { dg-error "constraint" }
+
+template <class>
+struct A {
+  template <auto V> static inline same_as<int> auto z = V; // { dg-error "constraint" }
+};
+
+int main() {
+  x<int>;          // { dg-bogus "" }
+  y<0>;            // { dg-bogus "" }
+  y<'0'>;          // { dg-message "required from here" }
+  A<void>::z<0>;   // { dg-bogus "" }
+  A<void>::z<'0'>; // { dg-message "required from here" }
+}
+
+// unsatisfied placeholder type constraint diagnosed at parse time
+template <class T> same_as<int> auto w = true; // { dg-error "constraint" }


More information about the Gcc-cvs mailing list