C++ PATCH to Implement P0846R0, ADL and function templates [v3]

Marek Polacek polacek@redhat.com
Thu Nov 1 19:36:00 GMT 2018


On Thu, Nov 01, 2018 at 12:15:48PM -0400, Jason Merrill wrote:
> On 10/31/18 6:45 PM, Marek Polacek wrote:
> > On Mon, Oct 29, 2018 at 05:59:13PM -0400, Jason Merrill wrote:
> > > On 10/28/18 3:56 PM, Marek Polacek wrote:
> > > > This patch implements P0846R0: ADL and Function Templates that are not Visible
> > > > <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0846r0.html>
> > > > whereby a name for which a normal lookup produces either no result or finds one
> > > > or more functions and that is followed by a "<" would be treated as if a function
> > > > template name had been found and would cause ADL to be performed.  Thus e.g.
> > > > 
> > > > namespace N {
> > > >     struct S { };
> > > >     template<int X> void f(S);
> > > > }
> > > > 
> > > > void
> > > > bar (N::S s)
> > > > {
> > > >     f<3>(s);
> > > > }
> > > > 
> > > > will now compile; ADL will find N::f.  The gist of the approach I took is in
> > > > cp_parser_template_name and setting TEMPLATE_ID_TRY_ADL_P.
> > > 
> > > Why do you need that flag?  Can't you tell from the first operand of the
> > > TEMPLATE_ID_EXPR whether it's suitable?
> > 
> > I needed to distinguish between the two kinds of identifiers
> > cp_parser_template_name can return.  But I found out I can (ab)use
> > IDENTIFIER_BINDING for that.  If it's empty then name lookup didn't find
> > anything (push_binding wasn't called for such an id), so we should try ADL.
> > Otherwise it's a dependent name and we need to wait for instantiation.
> > 
> > > > There's something I'm not clear on; the proposal talks about an
> > > > unqualified-id followed by a <, which is also the case for
> > > > 
> > > >     a.foo < 1;
> > > > 
> > > > which is "postfix-expression . unqualified-id <", but treating "foo" as a
> > > > template name would break valid programs.  I don't think this proposal should
> > > > be in effect for class member accesses, so I've disabled it by using scoped_p
> > > > below.  See fn-template8.C for a complete testcase for this scenario.
> > > 
> > > Agreed; ADL doesn't apply to class member access, so it shouldn't apply
> > > here.  Sent mail to the core reflector.
> > 
> > Great, thanks for that.
> > 
> > > > @@ -7165,7 +7165,9 @@ cp_parser_postfix_expression (cp_parser *parser, bool address_p, bool cast_p,
> > > >    	    if (idk == CP_ID_KIND_UNQUALIFIED
> > > >    		|| idk == CP_ID_KIND_TEMPLATE_ID)
> > > >    	      {
> > > > -		if (identifier_p (postfix_expression))
> > > > +		if (identifier_p (postfix_expression)
> > > > +		    || (TREE_CODE (postfix_expression) == TEMPLATE_ID_EXPR
> > > > +			&& TEMPLATE_ID_TRY_ADL_P (postfix_expression)))
> > > 
> > > Here I think you can check whether the first operand is an identifier.
> > 
> > Changed, along with checking for IDENTIFIER_BINDING.  E.g. this is a test that
> > would fail if we tried ADL for a template-id with any identifier:
> > 
> >    template <int> struct X {
> >      X() { fn<>(0); }
> >      template <int> void fn();
> >    };
> > 
> > here we need to perform two-stage lookup.
> 
> I don't see why; when we look up fn we find the member template, and we
> should remember that.  I think this code:
> 
> >   /* If DECL is dependent, and refers to a function, then just return
> > its name; we will look it up again during template instantiation.  */
> >   if (DECL_FUNCTION_TEMPLATE_P (decl) || !DECL_P (decl))
> >     {
> >       tree scope = ovl_scope (decl);
> >       if (TYPE_P (scope) && dependent_type_p (scope))
> >         return identifier;
> >     }
> 
> should go so that returning an identifier can consistently mean nothing
> found.

Woo!  That's nice that I can do that: it simplifies other stuff.  Removing
this hunk triggered an assert in tsubst_copy (just one testcase), but when
I moved it, everything seemed to work well.

So this is a version which doesn't use IDENTIFIER_BINDING at all.  Thanks,

Bootstrapped/regtested on x86_64-linux, ok for trunk?

2018-11-01  Marek Polacek  <polacek@redhat.com>

	Implement P0846R0, ADL and function templates.
	* decl.c (grokfndecl): Allow FUNCTION_DECL in assert.
	* lex.c (unqualified_fn_lookup_error): Handle TEMPLATE_ID_EXPR.
	* parser.c (cp_parser_postfix_expression): Do ADL for a template-name.
	(cp_parser_template_id): Give errors if parsing the template argument
	list didn't go well.  Allow FUNCTION_DECL in assert.
	(cp_parser_template_name): Consider a name to refer to a template if
	it is an unqualified-id followed by a <.  Don't return the identifier
	if the decl is a function and dependent.
	* pt.c (tsubst_copy) <case OVERLOAD>: Remove assert.

	* g++.dg/addr_builtin-1.C: Adjust dg-error.
	* g++.dg/cpp2a/fn-template1.C: New test.
	* g++.dg/cpp2a/fn-template10.C: New test.
	* g++.dg/cpp2a/fn-template11.C: New test.
	* g++.dg/cpp2a/fn-template12.C: New test.
	* g++.dg/cpp2a/fn-template13.C: New test.
	* g++.dg/cpp2a/fn-template14.C: New test.
	* g++.dg/cpp2a/fn-template15.C: New test.
	* g++.dg/cpp2a/fn-template16.C: New test.
	* g++.dg/cpp2a/fn-template2.C: New test.
	* g++.dg/cpp2a/fn-template3.C: New test.
	* g++.dg/cpp2a/fn-template4.C: New test.
	* g++.dg/cpp2a/fn-template5.C: New test.
	* g++.dg/cpp2a/fn-template6.C: New test.
	* g++.dg/cpp2a/fn-template7.C: New test.
	* g++.dg/cpp2a/fn-template8.C: New test.
	* g++.dg/cpp2a/fn-template9.C: New test.
	* g++.dg/parse/fn-template1.C: New test.
	* g++.dg/parse/fn-template2.C: New test.
	* g++.dg/parse/template19.C: Adjust dg-error.
	* g++.dg/template/pr61745.C: Add target to dg-error.

diff --git gcc/cp/decl.c gcc/cp/decl.c
index 23fcf6b0471..f0033dd5458 100644
--- gcc/cp/decl.c
+++ gcc/cp/decl.c
@@ -8857,7 +8857,9 @@ grokfndecl (tree ctype,
 	     the information in the TEMPLATE_ID_EXPR.  */
 	  SET_DECL_IMPLICIT_INSTANTIATION (decl);
 
-	  gcc_assert (identifier_p (fns) || TREE_CODE (fns) == OVERLOAD);
+	  gcc_assert (identifier_p (fns)
+		      || TREE_CODE (fns) == OVERLOAD
+		      || TREE_CODE (fns) == FUNCTION_DECL);
 	  DECL_TEMPLATE_INFO (decl) = build_template_info (fns, args);
 
 	  for (t = TYPE_ARG_TYPES (TREE_TYPE (decl)); t; t = TREE_CHAIN (t))
diff --git gcc/cp/lex.c gcc/cp/lex.c
index 410dfd1bc5b..26ec52f3498 100644
--- gcc/cp/lex.c
+++ gcc/cp/lex.c
@@ -541,6 +541,9 @@ unqualified_fn_lookup_error (cp_expr name_expr)
   if (loc == UNKNOWN_LOCATION)
     loc = input_location;
 
+  if (TREE_CODE (name) == TEMPLATE_ID_EXPR)
+    name = TREE_OPERAND (name, 0);
+
   if (processing_template_decl)
     {
       /* In a template, it is invalid to write "f()" or "f(3)" if no
diff --git gcc/cp/parser.c gcc/cp/parser.c
index 206ceb048d4..263eaddd143 100644
--- gcc/cp/parser.c
+++ gcc/cp/parser.c
@@ -7195,7 +7195,11 @@ cp_parser_postfix_expression (cp_parser *parser, bool address_p, bool cast_p,
 	    if (idk == CP_ID_KIND_UNQUALIFIED
 		|| idk == CP_ID_KIND_TEMPLATE_ID)
 	      {
-		if (identifier_p (postfix_expression))
+		if (identifier_p (postfix_expression)
+		    /* In C++2A, we may need to perform ADL for a template
+		       name.  */
+		    || (TREE_CODE (postfix_expression) == TEMPLATE_ID_EXPR
+			&& identifier_p (TREE_OPERAND (postfix_expression, 0))))
 		  {
 		    if (!args->is_empty ())
 		      {
@@ -16029,6 +16033,37 @@ cp_parser_template_id (cp_parser *parser,
 	}
       /* Parse the arguments.  */
       arguments = cp_parser_enclosed_template_argument_list (parser);
+
+      if ((cxx_dialect > cxx17)
+	  && (TREE_CODE (templ) == FUNCTION_DECL || identifier_p (templ))
+	  && !template_keyword_p
+	  && (cp_parser_error_occurred (parser)
+	      || cp_lexer_next_token_is_not (parser->lexer, CPP_OPEN_PAREN)))
+	{
+	  /* This didn't go well.  */
+	  if (TREE_CODE (templ) == FUNCTION_DECL)
+	    {
+	      /* C++2A says that "function-name < a;" is now ill-formed.  */
+	      if (cp_parser_error_occurred (parser))
+		{
+		  error_at (token->location, "invalid template-argument-list");
+		  inform (token->location, "function name as the left hand "
+			  "operand of %<<%> is ill-formed in C++2a; wrap the "
+			  "function name in %<()%>");
+		}
+	      else
+		/* We expect "f<targs>" to be followed by "(args)".  */
+		error_at (cp_lexer_peek_token (parser->lexer)->location,
+			  "expected %<(%> after template-argument-list");
+	      if (start_of_id)
+		/* Purge all subsequent tokens.  */
+		cp_lexer_purge_tokens_after (parser->lexer, start_of_id);
+	    }
+	  else
+	    cp_parser_simulate_error (parser);
+	  pop_deferring_access_checks ();
+	  return error_mark_node;
+	}
     }
 
   /* Set the location to be of the form:
@@ -16085,6 +16120,7 @@ cp_parser_template_id (cp_parser *parser,
 	 a function-template.  */
       gcc_assert ((DECL_FUNCTION_TEMPLATE_P (templ)
 		   || TREE_CODE (templ) == OVERLOAD
+		   || TREE_CODE (templ) == FUNCTION_DECL
 		   || BASELINK_P (templ)));
 
       template_id = lookup_template_function (templ, arguments);
@@ -16287,6 +16323,10 @@ cp_parser_template_name (cp_parser* parser,
 	}
     }
 
+  /* cp_parser_lookup_name clears OBJECT_TYPE.  */
+  const bool scoped_p = ((parser->scope ? parser->scope
+			  : parser->context->object_type) != NULL_TREE);
+
   /* Look up the name.  */
   decl = cp_parser_lookup_name (parser, identifier,
 				tag_type,
@@ -16319,6 +16359,25 @@ cp_parser_template_name (cp_parser* parser,
 	if (TREE_CODE (*iter) == TEMPLATE_DECL)
 	  found = true;
 
+      if (!found
+	  && (cxx_dialect > cxx17)
+	  && !scoped_p
+	  && cp_lexer_next_token_is (parser->lexer, CPP_LESS))
+	{
+	  /* [temp.names] says "A name is also considered to refer to a template
+	     if it is an unqualified-id followed by a < and name lookup finds
+	     either one or more functions or finds nothing."  */
+
+	  /* The "more functions" case.  Just use the OVERLOAD as normally.  */
+	  if (TREE_CODE (decl) == OVERLOAD
+	      /* Name lookup found one function.  */
+	      || TREE_CODE (decl) == FUNCTION_DECL)
+	    found = true;
+	  /* Name lookup found nothing.  */
+	  else if (decl == error_mark_node)
+	    return identifier;
+	}
+
       if (!found)
 	{
 	  /* The name does not name a template.  */
@@ -16327,15 +16386,6 @@ cp_parser_template_name (cp_parser* parser,
 	}
     }
 
-  /* If DECL is dependent, and refers to a function, then just return
-     its name; we will look it up again during template instantiation.  */
-  if (DECL_FUNCTION_TEMPLATE_P (decl) || !DECL_P (decl))
-    {
-      tree scope = ovl_scope (decl);
-      if (TYPE_P (scope) && dependent_type_p (scope))
-	return identifier;
-    }
-
   return decl;
 }
 
diff --git gcc/cp/pt.c gcc/cp/pt.c
index 2dc0cb1629c..4226d4d6b5c 100644
--- gcc/cp/pt.c
+++ gcc/cp/pt.c
@@ -15538,10 +15538,6 @@ tsubst_copy (tree t, tree args, tsubst_flags_t complain, tree in_decl)
       return t;
 
     case OVERLOAD:
-      /* An OVERLOAD will always be a non-dependent overload set; an
-	 overload set from function scope will just be represented with an
-	 IDENTIFIER_NODE, and from class scope with a BASELINK.  */
-      gcc_assert (!uses_template_parms (t));
       /* We must have marked any lookups as persistent.  */
       gcc_assert (!OVL_LOOKUP_P (t) || OVL_USED_P (t));
       return t;
diff --git gcc/testsuite/g++.dg/addr_builtin-1.C gcc/testsuite/g++.dg/addr_builtin-1.C
index e8ba31f994c..0c282b1f8cd 100644
--- gcc/testsuite/g++.dg/addr_builtin-1.C
+++ gcc/testsuite/g++.dg/addr_builtin-1.C
@@ -108,7 +108,7 @@ static F* test_taking_address_of_gcc_builtin ()
   a = p - __builtin_trap;            // { dg-error "built-in" }
 
   // Relational operators.  Ill-formed but allowed with -fpermissive.
-  a = __builtin_trap < p;            // { dg-error "built-in" }
+  a = __builtin_trap < p;            // { dg-error "built-in|invalid template-argument-list" }
   a = p < __builtin_trap;            // { dg-error "built-in" }
 
   a = __builtin_trap <= p;           // { dg-error "built-in" }
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template1.C gcc/testsuite/g++.dg/cpp2a/fn-template1.C
new file mode 100644
index 00000000000..2492d9df7d5
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template1.C
@@ -0,0 +1,37 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+int h;
+void g();
+void e();
+void e(int);
+void e(int, int);
+
+namespace N {
+  struct A { };
+  template <class T> int f(T);
+  template <class T> int g(T);
+  template <class T> int h(T);
+  template <class T> int e(T);
+}
+
+int v = e<N::A>(N::A());
+int x = f<N::A>(N::A());
+int y = g<N::A>(N::A());
+int z = h<N::A>(N::A()); // { dg-error "expected" }
+
+template<class>
+void fn ()
+{
+  int v = e<N::A>(N::A());
+  int x = f<N::A>(N::A());
+  int y = g<N::A>(N::A());
+  int z = h<N::A>(N::A()); // { dg-error "expected" }
+}
+
+void
+test ()
+{
+  fn<int>();
+}
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template10.C gcc/testsuite/g++.dg/cpp2a/fn-template10.C
new file mode 100644
index 00000000000..c69d48fa9b2
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template10.C
@@ -0,0 +1,22 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+int h;
+void g();
+void e();
+void e(int);
+void e(int, int);
+
+namespace N {
+  struct A { };
+  template <class T> static int f(T) { return 1; }
+  template <class T> static int g(T) { return 2; }
+  template <class T> static int h(T);
+  template <class T> static int e(T) { return 3; }
+}
+
+int v = e<N::A>(N::A());
+int x = f<N::A>(N::A());
+int y = g<N::A>(N::A());
+int z = h<N::A>(N::A()); // { dg-error "expected" }
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template11.C gcc/testsuite/g++.dg/cpp2a/fn-template11.C
new file mode 100644
index 00000000000..1a6b6882900
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template11.C
@@ -0,0 +1,11 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+int nonconst ();
+
+int foo ()
+{
+  return blah < // { dg-error "not declared" }
+    nonconst (), nonconst (); // { dg-error "call to non-.constexpr. function" }
+}
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template12.C gcc/testsuite/g++.dg/cpp2a/fn-template12.C
new file mode 100644
index 00000000000..fc72fd00584
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template12.C
@@ -0,0 +1,33 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+struct S {
+  template<typename T> int foo(T);
+  template<typename T> int foo(T, T);
+  template<typename T> int foo(T, T, T);
+};
+
+template<typename T>
+struct W {
+  template<typename U> T foo(U);
+  template<typename U> T foo(U, U);
+  template<typename U> T foo(U, U, U);
+};
+
+void
+test ()
+{
+  S s;
+  s.foo<int>(1);
+  s.foo<int>(1, 2);
+  s.foo<int>(1, 2, 3);
+
+  W<int> w;
+  w.foo<int>(1);
+  w.foo<int>(1, 2);
+  w.foo<int>(1, 2, 3);
+
+  w.nothere<int>(1); // { dg-error "has no member|expected" }
+  s.nothere<int>(1); // { dg-error "has no member|expected" }
+}
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template13.C gcc/testsuite/g++.dg/cpp2a/fn-template13.C
new file mode 100644
index 00000000000..ece6d152601
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template13.C
@@ -0,0 +1,32 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+struct A {
+  template<typename T>
+  int foo (T a, T b) { return a + b; }
+};
+
+int
+bar (A* pa, int (A::*pm)(int, int))
+{
+  return (pa->*pm)(1, 2);
+}
+
+int
+baz (A pa, int (A::*pm)(int, int))
+{
+  return (pa.*pm)(1, 2);
+}
+
+int
+main ()
+{
+  A a;
+  int i = bar (&a, &A::foo<int>);
+  if (i != 3)
+    __builtin_abort ();
+  i = baz (a, &A::foo<int>);
+  if (i != 3)
+    __builtin_abort ();
+}
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template14.C gcc/testsuite/g++.dg/cpp2a/fn-template14.C
new file mode 100644
index 00000000000..96d9267ff16
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template14.C
@@ -0,0 +1,9 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+template<typename> struct B
+{
+  template<typename> int foo() { return 0; }
+  int i = foo<int>();
+};
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template15.C gcc/testsuite/g++.dg/cpp2a/fn-template15.C
new file mode 100644
index 00000000000..20e48014be1
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template15.C
@@ -0,0 +1,23 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+// Don't get confused by these valid cases.
+
+template <class>
+class A {
+  template <bool> void b();
+  void m_fn1();
+};
+
+template <class T>
+void A<T>::m_fn1() { b<>(0); }
+
+
+template <int> struct X {
+  X() { fn<>(0); }
+  template <int> void fn();
+};
+
+
+template <typename> void a() { a<int>; }
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template16.C gcc/testsuite/g++.dg/cpp2a/fn-template16.C
new file mode 100644
index 00000000000..becaff1e3fb
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template16.C
@@ -0,0 +1,20 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+struct undeclared<int> { }; // { dg-error "not a class template" }
+
+int
+main ()
+{
+  int foo ();
+  int foo (int);
+  int foo (int, int);
+  int a, b = 10;
+  a = foo<; // { dg-error "" }
+  a = foo < b; // { dg-error "" }
+  a = foo<b>; // { dg-error "" }
+  a = foo<b>(; // { dg-error "expected" }
+  a = foo<b>(1; // { dg-error "expected" }
+  a = foo<b>(1); // { dg-error "no matching" }
+}
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template2.C gcc/testsuite/g++.dg/cpp2a/fn-template2.C
new file mode 100644
index 00000000000..f974c8c2cf9
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template2.C
@@ -0,0 +1,16 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+struct A { };
+bool operator <(void (*fp)(), A) { return false; }
+void f() {}
+
+int
+main ()
+{
+  A a;
+  f < a; // { dg-error "invalid" }
+  bool b = f < a; // { dg-error "invalid" }
+  (f) < a;
+}
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template3.C gcc/testsuite/g++.dg/cpp2a/fn-template3.C
new file mode 100644
index 00000000000..f801625ab3d
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template3.C
@@ -0,0 +1,29 @@
+// P0846R0
+// { dg-do run }
+// { dg-options "-std=c++2a" }
+
+void g();
+void e();
+void e(int);
+void e(int, int);
+
+namespace N {
+  struct A { };
+  template <class T> int f(T) { return 1; }
+  template <class T> int g(T) { return 2; }
+  template <class T> int e(T) { return 3; }
+}
+
+int
+main ()
+{
+  int v = e<N::A>(N::A());
+  if (v != 3)
+    __builtin_abort ();
+  int x = f<N::A>(N::A());
+  if (x != 1)
+    __builtin_abort ();
+  int y = g<N::A>(N::A());
+  if (y != 2)
+    __builtin_abort ();
+}
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template4.C gcc/testsuite/g++.dg/cpp2a/fn-template4.C
new file mode 100644
index 00000000000..9259c2ebf23
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template4.C
@@ -0,0 +1,11 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+template <typename T> void foo() { }
+template <typename T> void bar(int) { }
+int main()
+{
+  foo<float>();
+  bar<int>(1);
+}
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template5.C gcc/testsuite/g++.dg/cpp2a/fn-template5.C
new file mode 100644
index 00000000000..33477c96746
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template5.C
@@ -0,0 +1,32 @@
+// P0846R0
+// { dg-do run }
+// { dg-options "-std=c++2a" }
+
+int g() { return 11; }
+int e() { return 12; }
+int e(int) { return 13; }
+int e(int, int) { return 14; }
+
+namespace N {
+  struct A { };
+  template <class T> int f(T) { return 1; }
+  template <class T> int g(T) { return 2; }
+  template <class T> int e(T) { return 3; }
+}
+
+int
+main ()
+{
+  int v = e(1);
+  if (v != 13)
+    __builtin_abort ();
+  int x = e(1, 2);
+  if (x != 14)
+    __builtin_abort ();
+  int y = g();
+  if (y != 11)
+    __builtin_abort ();
+  int z = e();
+  if (z != 12)
+    __builtin_abort ();
+}
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template6.C gcc/testsuite/g++.dg/cpp2a/fn-template6.C
new file mode 100644
index 00000000000..63b2377bc6e
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template6.C
@@ -0,0 +1,16 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+template<class>
+struct X {
+  int first = 0;
+};
+
+int
+f ()
+{
+  X<int> x, y;
+  bool b = x.first < y.first;
+  return b;
+}
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template7.C gcc/testsuite/g++.dg/cpp2a/fn-template7.C
new file mode 100644
index 00000000000..d048606c0d6
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template7.C
@@ -0,0 +1,18 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+struct undeclared<int> { }; // { dg-error "not a class template" }
+
+int
+main ()
+{
+  int foo ();
+  int a, b = 10;
+  a = foo<; // { dg-error "invalid template-argument-list|invalid" }
+  a = foo < b; // { dg-error "invalid template-argument-list|invalid" }
+  a = foo<b>; // { dg-error "after template-argument-list|invalid" }
+  a = foo<b>(; // { dg-error "expected" }
+  a = foo<b>(1; // { dg-error "expected" }
+  a = foo<b>(1); // { dg-error "no matching" }
+}
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template8.C gcc/testsuite/g++.dg/cpp2a/fn-template8.C
new file mode 100644
index 00000000000..9cd28eed5d0
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template8.C
@@ -0,0 +1,34 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+const unsigned long arr[10] = { 2 };
+template<class T> struct S { int n; };
+
+template <class T>
+int fn1 (S<T>* s)
+{
+  int i = 1;
+  return s->n < arr[i + 1];
+}
+
+template <class T>
+int fn2 (S<T> s)
+{
+  int i = 1;
+  return s.n < arr[i + 1];
+}
+
+template <class T>
+int fn3 (S<T>* s)
+{
+  int i = 1;
+  return s->template n < 1; // { dg-error "parse error in template argument list" }
+}
+
+template <class T>
+int fn4 (S<T> s)
+{
+  int i = 1;
+  return s.template n < 1; // { dg-error "parse error in template argument list" }
+}
diff --git gcc/testsuite/g++.dg/cpp2a/fn-template9.C gcc/testsuite/g++.dg/cpp2a/fn-template9.C
new file mode 100644
index 00000000000..19c960cc936
--- /dev/null
+++ gcc/testsuite/g++.dg/cpp2a/fn-template9.C
@@ -0,0 +1,21 @@
+// P0846R0
+// { dg-do compile }
+// { dg-options "-std=c++2a" }
+
+namespace N1 {
+  struct S {};
+  template<int X> void f(S);
+}
+
+namespace N2 {
+  template<class T> void f(T t);
+}
+
+void
+g (N1::S s)
+{
+  f<3>(s);
+
+  using N2::f;
+  f<3>(s);
+}
diff --git gcc/testsuite/g++.dg/parse/fn-template1.C gcc/testsuite/g++.dg/parse/fn-template1.C
new file mode 100644
index 00000000000..00f8b4920e1
--- /dev/null
+++ gcc/testsuite/g++.dg/parse/fn-template1.C
@@ -0,0 +1,15 @@
+// P0846R0
+// { dg-do compile }
+
+struct A { };
+bool operator <(void (*fp)(), A) { return false; }
+void f() {}
+
+int
+main ()
+{
+  A a;
+  f < a; // { dg-error "invalid" "" { target c++2a } }
+  bool b = f < a; // { dg-error "invalid" "" { target c++2a } }
+  (f) < a;
+}
diff --git gcc/testsuite/g++.dg/parse/fn-template2.C gcc/testsuite/g++.dg/parse/fn-template2.C
new file mode 100644
index 00000000000..c56694efb92
--- /dev/null
+++ gcc/testsuite/g++.dg/parse/fn-template2.C
@@ -0,0 +1,17 @@
+// P0846R0
+// { dg-do compile }
+
+namespace N1 {
+  struct S {};
+  template<int X> void f(S);
+}
+
+namespace N2 {
+  template<class T> void f(T t);
+}
+
+void
+g (N1::S s)
+{
+  f<3>(s); // { dg-error "was not declared" "" { target c++17_down } }
+}
diff --git gcc/testsuite/g++.dg/parse/template19.C gcc/testsuite/g++.dg/parse/template19.C
index dc1a67334b5..fba4f6d0bf2 100644
--- gcc/testsuite/g++.dg/parse/template19.C
+++ gcc/testsuite/g++.dg/parse/template19.C
@@ -6,6 +6,6 @@ template<int> struct A
 {
   template<int> void foo()
   {
-    foo<0>::; // { dg-error "before" }
+    foo<0>::; // { dg-error "before|function template-id" }
   }
 };
diff --git gcc/testsuite/g++.dg/template/pr61745.C gcc/testsuite/g++.dg/template/pr61745.C
index 0f7c280e52a..da5973e3867 100644
--- gcc/testsuite/g++.dg/template/pr61745.C
+++ gcc/testsuite/g++.dg/template/pr61745.C
@@ -18,5 +18,7 @@ public:
   // this compiles only if the following definition is moved
   // AFTER the friend declaration
   Zp  operator-() const { return Zp(p-val); }
-  friend Zp<INT,P> operator- <>(const Zp<INT,P>& a, const Zp<INT,P>& b); // { dg-error "declaration|expected" }
+  // In C++2A, we have an unqualified-id (operator-) followed by
+  // '<', and name lookup found a function.
+  friend Zp<INT,P> operator- <>(const Zp<INT,P>& a, const Zp<INT,P>& b); // { dg-error "declaration|expected" "" { target c++17_down } }
 };



More information about the Gcc-patches mailing list