diff mbox

C++ PATCH for c++/69005 (infinite template recursion with template constructor)

Message ID 567CB6F3.6080103@redhat.com
State New
Headers show

Commit Message

Jason Merrill Dec. 25, 2015, 3:24 a.m. UTC
In this testcase, the exception specification for the implicitly-defined 
copy constructor for Foo depends on which function<...> constructor it 
calls; when figuring that out we consider the template constructor as a 
possible candidate, and substituting into the default template argument 
ends up depending on the copy constructor for Foo, leading to infinite 
recursion.

This is questionable code, but since the standard says that the template 
would never be instantiated to provide function(function), we can avoid 
this recursion by disqualifying the template before we get as far as 
substituting into the default argument.

Tested x86_64-pc-linux-gnu, applying to trunk.
diff mbox

Patch

commit 56e19df1ec61658aa4265941a0743c948237d390
Author: Jason Merrill <jason@redhat.com>
Date:   Mon Dec 21 16:59:33 2015 -0500

    	PR c++/69005
    
    	* call.c (add_template_candidate_real): Don't try to deduce X(X).

diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index cdfa01a..4f25356 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -3037,6 +3037,34 @@  add_template_candidate_real (struct z_candidate **candidates, tree tmpl,
   if (len < skip_without_in_chrg)
     return NULL;
 
+  if (DECL_CONSTRUCTOR_P (tmpl) && nargs == 2
+      && same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (first_arg),
+						    TREE_TYPE ((*arglist)[0])))
+    {
+      /* 12.8/6 says, "A declaration of a constructor for a class X is
+	 ill-formed if its first parameter is of type (optionally cv-qualified)
+	 X and either there are no other parameters or else all other
+	 parameters have default arguments. A member function template is never
+	 instantiated to produce such a constructor signature."
+
+	 So if we're trying to copy an object of the containing class, don't
+	 consider a template constructor that has a first parameter type that
+	 is just a template parameter, as we would deduce a signature that we
+	 would then reject in the code below.  */
+      if (tree firstparm = FUNCTION_FIRST_USER_PARMTYPE (tmpl))
+	{
+	  firstparm = TREE_VALUE (firstparm);
+	  if (PACK_EXPANSION_P (firstparm))
+	    firstparm = PACK_EXPANSION_PATTERN (firstparm);
+	  if (TREE_CODE (firstparm) == TEMPLATE_TYPE_PARM)
+	    {
+	      gcc_assert (!explicit_targs);
+	      reason = invalid_copy_with_fn_template_rejection ();
+	      goto fail;
+	    }
+	}
+    }
+
   nargs_without_in_chrg = ((first_arg_without_in_chrg != NULL_TREE ? 1 : 0)
 			   + (len - skip_without_in_chrg));
   args_without_in_chrg = XALLOCAVEC (tree, nargs_without_in_chrg);
@@ -3075,34 +3103,15 @@  add_template_candidate_real (struct z_candidate **candidates, tree tmpl,
       goto fail;
     }
 
-  /* In [class.copy]:
-
-       A member function template is never instantiated to perform the
-       copy of a class object to an object of its class type.
-
-     It's a little unclear what this means; the standard explicitly
-     does allow a template to be used to copy a class.  For example,
-     in:
-
-       struct A {
-	 A(A&);
-	 template <class T> A(const T&);
-       };
-       const A f ();
-       void g () { A a (f ()); }
-
-     the member template will be used to make the copy.  The section
-     quoted above appears in the paragraph that forbids constructors
-     whose only parameter is (a possibly cv-qualified variant of) the
-     class type, and a logical interpretation is that the intent was
-     to forbid the instantiation of member templates which would then
-     have that form.  */
   if (DECL_CONSTRUCTOR_P (fn) && nargs == 2)
     {
       tree arg_types = FUNCTION_FIRST_USER_PARMTYPE (fn);
       if (arg_types && same_type_p (TYPE_MAIN_VARIANT (TREE_VALUE (arg_types)),
 				    ctype))
 	{
+	  /* We're trying to produce a constructor with a prohibited signature,
+	     as discussed above; handle here any cases we didn't catch then,
+	     such as X(X<T>).  */
 	  reason = invalid_copy_with_fn_template_rejection ();
 	  goto fail;
 	}
diff --git a/gcc/testsuite/g++.dg/cpp0x/defaulted50.C b/gcc/testsuite/g++.dg/cpp0x/defaulted50.C
new file mode 100644
index 0000000..fea4537
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/defaulted50.C
@@ -0,0 +1,27 @@ 
+// PR c++/69005
+// { dg-do compile { target c++11 } }
+
+template<typename T> T& declval();
+
+template<typename _Sig> class function;
+
+template<typename _Res, typename _Arg>
+struct function<_Res(_Arg)>
+{
+  function() noexcept { }
+
+  function(const function&) { }
+
+  template<typename _Functor,
+	   typename = decltype(declval<_Functor&>()(declval<_Arg>()))>
+  function(_Functor) { }
+
+  _Res operator()(_Arg) const;
+};
+
+struct Foo {
+  function<void(Foo)> Func;
+};
+
+extern Foo exfoo;
+Foo f (exfoo);