public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: Jason Merrill <jason@redhat.com>
To: Patrick Palka <ppalka@redhat.com>, gcc-patches@gcc.gnu.org
Subject: Re: [PATCH] c++: check arity before deduction w/ explicit targs [PR12672]
Date: Tue, 31 Aug 2021 11:53:49 -0400	[thread overview]
Message-ID: <46d788f9-67fb-735e-7449-95bad3a6d1a2@redhat.com> (raw)
In-Reply-To: <20210831012638.3293672-1-ppalka@redhat.com>

On 8/30/21 9:26 PM, Patrick Palka wrote:
> 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.
> 
> 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
> saw some compile-time improvements of about 5% but also some regressions
> by 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
> yielding a function type with an added 'const', but I think this is
> permitted by [dcl.fct]/7, so I changed the test accordingly.

Agreed.

> Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for
> trunk?

OK, thanks.

> 	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-args6.C: New test.
> ---
>   gcc/cp/call.c                                 | 67 ++++++++++++++++---
>   gcc/testsuite/g++.dg/cpp0x/decltype29.C       |  4 +-
>   gcc/testsuite/g++.dg/template/error56.C       |  4 +-
>   .../g++.dg/template/explicit-args6.C          | 33 +++++++++
>   gcc/testsuite/g++.old-deja/g++.pt/unify6.C    |  4 +-
>   5 files changed, 96 insertions(+), 16 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/template/explicit-args6.C
> 
> diff --git a/gcc/cp/call.c b/gcc/cp/call.c
> index e4df72ec1a3..80e6121ce44 100644
> --- a/gcc/cp/call.c
> +++ b/gcc/cp/call.c
> @@ -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);
> diff --git a/gcc/testsuite/g++.dg/cpp0x/decltype29.C b/gcc/testsuite/g++.dg/cpp0x/decltype29.C
> index 51da8ddd0de..ea97b033569 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/decltype29.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/decltype29.C
> @@ -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" }
> diff --git a/gcc/testsuite/g++.dg/template/error56.C b/gcc/testsuite/g++.dg/template/error56.C
> index e85471a50b0..71206a1ae5c 100644
> --- a/gcc/testsuite/g++.dg/template/error56.C
> +++ b/gcc/testsuite/g++.dg/template/error56.C
> @@ -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-args6.C b/gcc/testsuite/g++.dg/template/explicit-args6.C
> new file mode 100644
> index 00000000000..fb5e89e4cf6
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/template/explicit-args6.C
> @@ -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);
> +}
> diff --git a/gcc/testsuite/g++.old-deja/g++.pt/unify6.C b/gcc/testsuite/g++.old-deja/g++.pt/unify6.C
> index d122ec2dcb9..ee14ceadd91 100644
> --- a/gcc/testsuite/g++.old-deja/g++.pt/unify6.C
> +++ b/gcc/testsuite/g++.old-deja/g++.pt/unify6.C
> @@ -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)();
> 


      reply	other threads:[~2021-08-31 15:53 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-08-31  1:26 Patrick Palka
2021-08-31 15:53 ` Jason Merrill [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=46d788f9-67fb-735e-7449-95bad3a6d1a2@redhat.com \
    --to=jason@redhat.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=ppalka@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).