On 12/16/21 14:53, Patrick Palka wrote:
On Thu, 16 Dec 2021, Patrick Palka wrote:

On Wed, 15 Dec 2021, Jason Merrill wrote:

On 12/15/21 12:49, Patrick Palka wrote:
This patch makes us remember the function selected by overload
resolution during ahead of time processing of a non-dependent call
expression, so that we avoid repeating most of the work of overload
resolution at instantiation time.  This mirrors what we already do for
non-dependent operator expressions via build_min_non_dep_op_overload.

Some caveats:

   * When processing ahead of time a non-dependent call to a member
     function template inside a class template (as in
     g++.dg/template/deduce4.C), we end up generating an "inverted" partial
     instantiation such as S<T>::foo<int, int>(), the kinds of which we're
     apparently not prepared to fully instantiate (e.g. tsubst_baselink
     mishandles it).  So this patch disables this optimization for such
     functions and adds a FIXME.

I wonder if it would be worthwhile to build a TEMPLATE_ID_EXPR to remember the
deduced template args, even if we are failing to remember the actual function?

Hmm, that transformation could have observable effects, since overload
resolution for f<int>(0) might end up instantiating more things than for
f(0) due to the explicit-args substitution step:

   template<class T> struct A { using type = typename T::type; };

   template<class T> void f(T);
   template<class T, class U = typename T::type> typename A<T>::type f(T);

Here overload resolution for f(0) succeeds and selects the first
overload but for f<int>(0) induces a hard error.  Also I worry that such
a transformation might affect declaration matching in weird ways due
to conflating f(0) with f<int>(0).

We could at least though prune the overload set to the corresponding
selected function template rather than the "inside-out" specialization;
I'll try to implement that.

Here's a bootstrapped and regtested patch which implements that:

OK.

-- >8 --

This patch makes us remember the function selected by overload resolution
during ahead of time processing of a non-dependent call expression, so
that we avoid repeating most of the work of overload resolution for the
call at instantiation time.  Note that we already do this for
non-dependent operator expressions via build_min_non_dep_op_overload.

Some caveats:

  * When processing ahead of time a non-dependent call to a member
    function template inside a common class template (as in
    g++.dg/template/deduce4.C), we end up generating an "inverted" partial
    instantiation such as S<T>::foo<int, int>(), the likes of which we're
    apparently not prepared to fully instantiate (e.g. tsubst_baselink
    mishandles it).  So this patch makes us prune to the selected
    template instead of the specialization in this case.

  * This change triggered a latent FUNCTION_DECL pretty printing issue
    in cpp0x/error2.C -- since we now resolve the call to foo<0> ahead
    of time, the error now looks like:

      error: expansion pattern ‘foo()()=0’ contains no parameter pack

    where the FUNCTION_DECL for foo<0> is clearly misprinted.  But this
    pretty-printing issue could be reproduced without this patch if
    we define foo as a non-template function.  Since this testcase was
    added to verify pretty printing of TEMPLATE_ID_EXPR, I work around
    this test failure by making the call to foo type-dependent and thus
    immune to this ahead of time pruning.

  * We now reject parts of cpp0x/fntmp-equiv1.C because we notice that
    the call d(f, b) in

      template <unsigned long f, unsigned b, typename> e<d(f, b)> d();

    is always non-constexpr because the selected d isn't.  I tried fixing
    this by making it constexpr, but then the call to d from main becomes
    ambiguous.  So I settled with removing this part of the testcase.

gcc/cp/ChangeLog:

        * call.c (build_new_method_call): For a non-dependent call
        expression inside a template, returning a templated tree
        whose overload set contains just the selected function.
        * semantics.c (finish_call_expr): Likewise.

gcc/testsuite/ChangeLog:

        * g++.dg/cpp0x/error2.C: Make the call to foo type-dependent in
        order to avoid latent pretty-printing issue for FUNCTION_DECL
        inside MODOP_EXPR.
        * g++.dg/cpp0x/fntmp-equiv1.C: Remove ill-formed parts of
        testcase that we now diagnose.
        * g++.dg/template/non-dependent16.C: New test.
        * g++.dg/template/non-dependent16a.C: New test.
        * g++.dg/template/non-dependent17.C: New test.
---
  gcc/cp/call.c                                 | 27 ++++++++++++++
  gcc/cp/semantics.c                            | 15 ++++++++
  gcc/testsuite/g++.dg/cpp0x/error2.C           |  4 +-
  gcc/testsuite/g++.dg/cpp0x/fntmp-equiv1.C     |  4 --
  .../g++.dg/template/non-dependent16.C         | 37 +++++++++++++++++++
  .../g++.dg/template/non-dependent16a.C        | 36 ++++++++++++++++++
  .../g++.dg/template/non-dependent17.C         | 21 +++++++++++
  7 files changed, 138 insertions(+), 6 deletions(-)
  create mode 100644 gcc/testsuite/g++.dg/template/non-dependent16.C
  create mode 100644 gcc/testsuite/g++.dg/template/non-dependent16a.C
  create mode 100644 gcc/testsuite/g++.dg/template/non-dependent17.C

diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index 495dcdd77b3..1fbfc580a1e 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -11163,6 +11163,33 @@ build_new_method_call (tree instance, tree fns, vec<tree, 
va_gc> **args,
        }
        if (INDIRECT_REF_P (call))
        call = TREE_OPERAND (call, 0);
+
+      /* Prune all but the selected function from the original overload
+        set so that we can avoid some duplicate work at instantiation time.  */
+      if (really_overloaded_fn (fns))
+       {
+         if (DECL_TEMPLATE_INFO (fn)
+             && DECL_MEMBER_TEMPLATE_P (DECL_TI_TEMPLATE (fn))
+             && dependent_type_p (DECL_CONTEXT (fn)))
+           {
+             /* FIXME: We're not prepared to fully instantiate "inside-out"
+                partial instantiations such as A<T>::f<int>().  So instead
+                use the selected template, not the specialization.  */
+
+             if (OVL_SINGLE_P (fns))
+               /* If the original overload set consists of a single function
+                  template, this isn't beneficial.  */
+               goto skip_prune;
+
+             fn = ovl_make (DECL_TI_TEMPLATE (fn));
+             if (template_only)
+               fn = lookup_template_function (fn, explicit_targs);
+           }
+         orig_fns = copy_node (orig_fns);
+         BASELINK_FUNCTIONS (orig_fns) = fn;
+       }
+
+skip_prune:
        call = (build_min_non_dep_call_vec
              (call,
               build_min (COMPONENT_REF, TREE_TYPE (CALL_EXPR_FN (call)),
diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
index 6603066c620..6ffd82cb0ec 100644
--- a/gcc/cp/semantics.c
+++ b/gcc/cp/semantics.c
@@ -2893,6 +2893,21 @@ finish_call_expr (tree fn, vec<tree, va_gc> **args, bool 
disallow_virtual,
      {
        if (INDIRECT_REF_P (result))
        result = TREE_OPERAND (result, 0);
+
+      /* Prune all but the selected function from the original overload
+        set so that we can avoid some duplicate work at instantiation time.  */
+      if (TREE_CODE (result) == CALL_EXPR
+         && really_overloaded_fn (orig_fn))
+       {
+         orig_fn = CALL_EXPR_FN (result);
+         if (TREE_CODE (orig_fn) == COMPONENT_REF)
+           {
+             /* The non-dependent result of build_new_method_call.  */
+             orig_fn = TREE_OPERAND (orig_fn, 1);
+             gcc_assert (BASELINK_P (orig_fn));
+           }
+       }
+
        result = build_call_vec (TREE_TYPE (result), orig_fn, orig_args);
        SET_EXPR_LOCATION (result, input_location);
        KOENIG_LOOKUP_P (result) = koenig_p;
diff --git a/gcc/testsuite/g++.dg/cpp0x/error2.C 
b/gcc/testsuite/g++.dg/cpp0x/error2.C
index e6af294c180..eb966362ccb 100644
--- a/gcc/testsuite/g++.dg/cpp0x/error2.C
+++ b/gcc/testsuite/g++.dg/cpp0x/error2.C
@@ -3,7 +3,7 @@
template<int> int foo(); -template<typename F> void bar(F f)
+template<typename F, int N> void bar(F f)
  {
-  f((foo<0>()=0)...); // { dg-error "pattern '\\(foo\\<0\\>\\)\\(\\)=0'" }
+  f((foo<N>()=0)...); // { dg-error "pattern '\\(foo\\<N\\>\\)\\(\\)=0'" }
  }
diff --git a/gcc/testsuite/g++.dg/cpp0x/fntmp-equiv1.C 
b/gcc/testsuite/g++.dg/cpp0x/fntmp-equiv1.C
index 833ae6fc85c..60ebad8d1d3 100644
--- a/gcc/testsuite/g++.dg/cpp0x/fntmp-equiv1.C
+++ b/gcc/testsuite/g++.dg/cpp0x/fntmp-equiv1.C
@@ -1,10 +1,7 @@
  // PR c++/86946, DR 1321
  // { dg-do compile { target c++11 } }
-int d(int, int);
  template <long> class e {};
-template <unsigned long f, unsigned b, typename> e<sizeof(d(f, b))> d();
-template <unsigned long f, unsigned b, typename> e<d(f, b)> d();
template <class T, class U> constexpr T d2(T, U) { return 42; }
  template <unsigned long f, unsigned b, typename> e<d2(f, b)> d2();
@@ -17,7 +14,6 @@ template <unsigned long f, unsigned b, typename> e<sizeof(d3(f, 
b))> d3();
int main()
  {
-  d<1,2,int>();
    d2<1,2,int>();
    d3<1,2,int>();
  }
diff --git a/gcc/testsuite/g++.dg/template/non-dependent16.C 
b/gcc/testsuite/g++.dg/template/non-dependent16.C
new file mode 100644
index 00000000000..ee8ef902529
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/non-dependent16.C
@@ -0,0 +1,37 @@
+// This test verifies that after resolving a non-dependent call expression
+// ahead of time, we prune all but the selected candidate from the overload
+// set.  Without this optimization, overload resolution for the final call to
+// f<void>() would be exponential in the size of the overload set.
+
+// { dg-do compile { target c++11 } }
+
+template<class T> void f();
+template<class T> auto f() -> decltype(f<void>(), 1, *T());
+template<class T> auto f() -> decltype(f<void>(), 2, *T());
+template<class T> auto f() -> decltype(f<void>(), 3, *T());
+template<class T> auto f() -> decltype(f<void>(), 4, *T());
+template<class T> auto f() -> decltype(f<void>(), 5, *T());
+template<class T> auto f() -> decltype(f<void>(), 6, *T());
+template<class T> auto f() -> decltype(f<void>(), 7, *T());
+template<class T> auto f() -> decltype(f<void>(), 8, *T());
+template<class T> auto f() -> decltype(f<void>(), 9, *T());
+template<class T> auto f() -> decltype(f<void>(), 10, *T());
+template<class T> auto f() -> decltype(f<void>(), 11, *T());
+template<class T> auto f() -> decltype(f<void>(), 12, *T());
+template<class T> auto f() -> decltype(f<void>(), 13, *T());
+template<class T> auto f() -> decltype(f<void>(), 14, *T());
+template<class T> auto f() -> decltype(f<void>(), 15, *T());
+template<class T> auto f() -> decltype(f<void>(), 16, *T());
+template<class T> auto f() -> decltype(f<void>(), 17, *T());
+template<class T> auto f() -> decltype(f<void>(), 18, *T());
+template<class T> auto f() -> decltype(f<void>(), 19, *T());
+template<class T> auto f() -> decltype(f<void>(), 20, *T());
+template<class T> auto f() -> decltype(f<void>(), 21, *T());
+template<class T> auto f() -> decltype(f<void>(), 22, *T());
+template<class T> auto f() -> decltype(f<void>(), 23, *T());
+template<class T> auto f() -> decltype(f<void>(), 24, *T());
+template<class T> auto f() -> decltype(f<void>(), 25, *T());
+
+int main() {
+  f<void>();
+}
diff --git a/gcc/testsuite/g++.dg/template/non-dependent16a.C 
b/gcc/testsuite/g++.dg/template/non-dependent16a.C
new file mode 100644
index 00000000000..0e04d646c0b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/non-dependent16a.C
@@ -0,0 +1,36 @@
+// Like non-dependent16.C, but using member functions.
+
+// { dg-do compile { target c++11 } }
+
+struct A {
+  template<class T> static void f();
+  template<class T> static auto f() -> decltype(f<void>(), 1, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 2, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 3, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 4, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 5, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 6, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 7, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 8, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 9, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 10, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 11, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 12, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 13, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 14, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 15, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 16, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 17, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 18, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 19, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 20, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 21, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 22, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 23, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 24, *T());
+  template<class T> static auto f() -> decltype(f<void>(), 25, *T());
+};
+
+int main() {
+  A::f<void>();
+}
diff --git a/gcc/testsuite/g++.dg/template/non-dependent17.C 
b/gcc/testsuite/g++.dg/template/non-dependent17.C
new file mode 100644
index 00000000000..bc664999e84
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/non-dependent17.C
@@ -0,0 +1,21 @@
+// A variant of deduce4.C with multiple overloads of foo.  Verify we don't
+// crash when after ahead-of-time pruning of the overload set for the
+// non-dependent call to foo.
+// { dg-do compile }
+
+template <typename T>
+struct S {
+  template <typename U, typename V>
+  static void foo(V) { }
+  template <typename U>
+  static void foo(...) { }
+
+  void bar () { foo<int>(10); }
+};
+
+void
+test ()
+{
+  S<int> s;
+  s.bar ();
+}

Reply via email to