c++: check arity before deduction w/ explicit targs [PR12672]
authorPatrick Palka <ppalka@redhat.com>
Tue, 31 Aug 2021 17:31:10 +0000 (13:31 -0400)
committerPatrick Palka <ppalka@redhat.com>
Tue, 31 Aug 2021 17:31:10 +0000 (13:31 -0400)
During overload resolution, when the arity of a function template
clearly disagrees with the arity of the call, no specialization of the
function template could yield a viable candidate.  The deduction routine
type_unification_real already notices this situation, but not before
it substitutes explicit template arguments into the template, a step
which could induce a hard error.  Although it's necessary to perform
this substitution first in order to check arity perfectly (since the
substitution can e.g. expand a non-trailing parameter pack), in most
cases we can determine ahead of time whether there's an arity
disagreement without needing to perform deduction at all.

To that end, this patch implements an (approximate) arity check in
add_template_candidate_real that guards actual deduction.  It's enabled
only when there are explicit template arguments since that's when
deduction can force otherwise avoidable template instantiations.  (I
experimented with enabling it unconditionally as an optimization, and
observed some improvements to compile time of about 5% but also some
slowdowns of about the same magnitude, so kept it conditional.)

In passing, this adds a least_p parameter to arity_rejection for sake
of consistent diagnostics with unify_arity.

A couple of testcases needed to be adjusted so that deduction continues
to occur as intended after this change.  Except in unify6.C, where we
were expecting foo<void ()> to be ill-formed due to substitution
forming a function type with an added 'const', but ISTM this is
permitted by [dcl.fct]/7, so I changed the test accordingly.

PR c++/12672

gcc/cp/ChangeLog:

* call.c (rejection_reason::call_varargs_p): Rename this
previously unused member to ...
(rejection_reason::least_p): ... this.
(arity_rejection): Add least_p parameter.
(add_template_candidate_real): When there are explicit
template arguments, check that the arity of the call agrees with
the arity of the function before attempting deduction.
(print_arity_information): Add least_p parameter.
(print_z_candidate): Adjust call to print_arity_information.

gcc/testsuite/ChangeLog:

* g++.dg/cpp0x/decltype29.C: Adjust.
* g++.dg/template/error56.C: Adjust.
* g++.old-deja/g++.pt/unify6.C: Adjust.
* g++.dg/template/explicit-args7.C: New test.

gcc/cp/call.c
gcc/testsuite/g++.dg/cpp0x/decltype29.C
gcc/testsuite/g++.dg/template/error56.C
gcc/testsuite/g++.dg/template/explicit-args7.C [new file with mode: 0644]
gcc/testsuite/g++.old-deja/g++.pt/unify6.C

index e4df72e..80e6121 100644 (file)
@@ -455,8 +455,8 @@ struct rejection_reason {
       int expected;
       /* The actual number of arguments in the call.  */
       int actual;
-      /* Whether the call was a varargs call.  */
-      bool call_varargs_p;
+      /* Whether EXPECTED should be treated as a lower bound.  */
+      bool least_p;
     } arity;
     /* Information about an argument conversion mismatch.  */
     struct conversion_info conversion;
@@ -628,12 +628,13 @@ alloc_rejection (enum rejection_reason_code code)
 }
 
 static struct rejection_reason *
-arity_rejection (tree first_arg, int expected, int actual)
+arity_rejection (tree first_arg, int expected, int actual, bool least_p = false)
 {
   struct rejection_reason *r = alloc_rejection (rr_arity);
   int adjust = first_arg != NULL_TREE;
   r->u.arity.expected = expected - adjust;
   r->u.arity.actual = actual - adjust;
+  r->u.arity.least_p = least_p;
   return r;
 }
 
@@ -3452,6 +3453,44 @@ add_template_candidate_real (struct z_candidate **candidates, tree tmpl,
     }
   gcc_assert (ia == nargs_without_in_chrg);
 
+  if (!obj && explicit_targs)
+    {
+      /* Check that there's no obvious arity mismatch before proceeding with
+        deduction.  This avoids substituting explicit template arguments
+        into the template (which could result in an error outside the
+        immediate context) when the resulting candidate would be unviable
+        anyway.  */
+      int min_arity = 0, max_arity = 0;
+      tree parms = TYPE_ARG_TYPES (TREE_TYPE (tmpl));
+      parms = skip_artificial_parms_for (tmpl, parms);
+      for (; parms != void_list_node; parms = TREE_CHAIN (parms))
+       {
+         if (!parms || PACK_EXPANSION_P (TREE_VALUE (parms)))
+           {
+             max_arity = -1;
+             break;
+           }
+         if (TREE_PURPOSE (parms))
+           /* A parameter with a default argument.  */
+           ++max_arity;
+         else
+           ++min_arity, ++max_arity;
+       }
+      if (ia < (unsigned)min_arity)
+       {
+         /* Too few arguments.  */
+         reason = arity_rejection (NULL_TREE, min_arity, ia,
+                                   /*least_p=*/(max_arity == -1));
+         goto fail;
+       }
+      else if (max_arity != -1 && ia > (unsigned)max_arity)
+       {
+         /* Too many arguments.  */
+         reason = arity_rejection (NULL_TREE, max_arity, ia);
+         goto fail;
+       }
+    }
+
   errs = errorcount+sorrycount;
   if (!obj)
     convs = alloc_conversions (nargs);
@@ -3725,12 +3764,19 @@ print_conversion_rejection (location_t loc, struct conversion_info *info,
    HAVE.  */
 
 static void
-print_arity_information (location_t loc, unsigned int have, unsigned int want)
-{
-  inform_n (loc, want,
-           "  candidate expects %d argument, %d provided",
-           "  candidate expects %d arguments, %d provided",
-           want, have);
+print_arity_information (location_t loc, unsigned int have, unsigned int want,
+                        bool least_p)
+{
+  if (least_p)
+    inform_n (loc, want,
+             "  candidate expects at least %d argument, %d provided",
+             "  candidate expects at least %d arguments, %d provided",
+             want, have);
+  else
+    inform_n (loc, want,
+             "  candidate expects %d argument, %d provided",
+             "  candidate expects %d arguments, %d provided",
+             want, have);
 }
 
 /* Print information about one overload candidate CANDIDATE.  MSGSTR
@@ -3794,7 +3840,8 @@ print_z_candidate (location_t loc, const char *msgstr,
        {
        case rr_arity:
          print_arity_information (cloc, r->u.arity.actual,
-                                  r->u.arity.expected);
+                                  r->u.arity.expected,
+                                  r->u.arity.least_p);
          break;
        case rr_arg_conversion:
          print_conversion_rejection (cloc, &r->u.conversion, fn);
index 51da8dd..ea97b03 100644 (file)
@@ -10,10 +10,10 @@ ft() {}
 
 template<class F, int N>
 decltype (ft<F> (F()))         // { dg-error "depth" }
-ft() {}
+ft(F) {}
 
 int main() {
-    ft<struct a*, 0>();                // { dg-message "from here" }
+    ft<struct a*, 0>(0);               // { dg-message "from here" }
 }
 
 // { dg-prune-output "compilation terminated" }
index e85471a..71206a1 100644 (file)
@@ -3,12 +3,12 @@
 struct A
 {
   template <class T> void f(T);
-  void f();
+  void f(int);
 };
 
 int main()
 {
-  A().f<1>();                  // { dg-error "f<1>" }
+  A().f<1>(0);                 // { dg-error "f<1>" }
   // { dg-error "type/value mismatch at argument 1" "" { target *-*-* } .-1 }
   // { dg-message "expected a type, got .1." "" { target *-*-* } .-2 }
 }
diff --git a/gcc/testsuite/g++.dg/template/explicit-args7.C b/gcc/testsuite/g++.dg/template/explicit-args7.C
new file mode 100644 (file)
index 0000000..fb5e89e
--- /dev/null
@@ -0,0 +1,33 @@
+// PR c++/12672
+// Verify we don't substitute explicit template arguments into
+// candidate function templates when the arity of the function
+// template disagrees with the arity of the call.
+
+template<class T>
+struct A { typedef typename T::type type; };
+
+template<class T> void f(T); // arity 1
+template<class T> void f(T, T, T); // arity 3
+
+template<class T> typename A<T>::type f(T, T); // arity 2
+template<class T, class U> typename A<T>::type f(U, U); // arity 2
+
+struct B {
+  template<class T> void f(T); // arity 1
+  template<class T> void f(T, T, T); // arity 3
+
+  template<class T> typename A<T>::type f(T, T); // arity 2
+  template<class T, class U> typename A<T>::type f(U, U); // arity 2
+};
+
+int main() {
+  // If overload resolution attempts deduction for any of the arity-2 function
+  // templates, the substitution of explicit arguments into the template would
+  // cause a hard error.
+  f<int>(1);
+  f<int>(1, 1, 1);
+
+  B b;
+  b.f<int>(1);
+  b.f<int>(1, 1, 1);
+}
index d122ec2..ee14cea 100644 (file)
@@ -23,8 +23,8 @@ template<class T> void foo(T const *){} // { dg-error "pointer to reference" }
 
 void f()
 {
-  foo<int &>(); // { dg-error "" } attempt to build int & const *
-  foo<void ()>(); // { dg-error "" } attempt to build void (const *)()
+  foo<int &>(0); // { dg-error "" } attempt to build int & const *
+  foo<void ()>(0); // OK by [dcl.fct]/7, the const is silently dropped
 }
 
 typedef void (*Fptr)();