c++: Implement C++26 P2893R3 - Variadic friends [PR114459]

Message ID ZjUSTFY+YKS6dYL0@tucnak
State New
Headers
Series c++: Implement C++26 P2893R3 - Variadic friends [PR114459] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Testing passed

Commit Message

Jakub Jelinek May 3, 2024, 4:35 p.m. UTC
  Hi!

The following patch imeplements the C++26 P2893R3 - Variadic friends
paper.  The paper allows for the friend type declarations to specify
more than one friend type specifier and allows to specify ... at
the end of each.  The patch doesn't introduce tentative parsing of
friend-type-declaration non-terminal, but rather just extends existing
parsing where it is a friend declaration which ends with ; after the
declaration specifiers to the cases where it ends with ...; or , or ...,
In that case it pedwarns for cxx_dialect < cxx26, handles the ... and
if there is , continues in a loop to parse the further friend type
specifiers.

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

2024-05-03  Jakub Jelinek  <jakub@redhat.com>

	PR c++/114459
gcc/c-family/
	* c-cppbuiltin.cc (c_cpp_builtins): Predefine
	__cpp_variadic_friend=202403L for C++26.
gcc/cp/
	* parser.cc (cp_parser_member_declaration): Implement C++26
	P2893R3 - Variadic friends.  Parse friend type declarations
	with ... or with more than one friend type specifier.
	* friend.cc (make_friend_class): Allow TYPE_PACK_EXPANSION.
	* pt.cc (instantiate_class_template): Handle PACK_EXPANSION_P
	in friend classes.
gcc/testsuite/
	* g++.dg/cpp26/feat-cxx26.C (__cpp_variadic_friend): Add test.
	* g++.dg/cpp26/variadic-friend1.C: New test.


	Jakub
  

Comments

Jason Merrill May 7, 2024, 8:27 p.m. UTC | #1
On 5/3/24 12:35, Jakub Jelinek wrote:
> Hi!
> 
> The following patch imeplements the C++26 P2893R3 - Variadic friends
> paper.  The paper allows for the friend type declarations to specify
> more than one friend type specifier and allows to specify ... at
> the end of each.  The patch doesn't introduce tentative parsing of
> friend-type-declaration non-terminal, but rather just extends existing
> parsing where it is a friend declaration which ends with ; after the
> declaration specifiers to the cases where it ends with ...; or , or ...,
> In that case it pedwarns for cxx_dialect < cxx26, handles the ... and
> if there is , continues in a loop to parse the further friend type
> specifiers.
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
> 
> 2024-05-03  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/114459
> gcc/c-family/
> 	* c-cppbuiltin.cc (c_cpp_builtins): Predefine
> 	__cpp_variadic_friend=202403L for C++26.
> gcc/cp/
> 	* parser.cc (cp_parser_member_declaration): Implement C++26
> 	P2893R3 - Variadic friends.  Parse friend type declarations
> 	with ... or with more than one friend type specifier.
> 	* friend.cc (make_friend_class): Allow TYPE_PACK_EXPANSION.
> 	* pt.cc (instantiate_class_template): Handle PACK_EXPANSION_P
> 	in friend classes.
> gcc/testsuite/
> 	* g++.dg/cpp26/feat-cxx26.C (__cpp_variadic_friend): Add test.
> 	* g++.dg/cpp26/variadic-friend1.C: New test.
> 
> --- gcc/c-family/c-cppbuiltin.cc.jj	2024-05-02 09:31:17.746298275 +0200
> +++ gcc/c-family/c-cppbuiltin.cc	2024-05-03 14:50:08.008242950 +0200
> @@ -1093,6 +1093,7 @@ c_cpp_builtins (cpp_reader *pfile)
>   	  cpp_define (pfile, "__cpp_placeholder_variables=202306L");
>   	  cpp_define (pfile, "__cpp_structured_bindings=202403L");
>   	  cpp_define (pfile, "__cpp_deleted_function=202403L");
> +	  cpp_define (pfile, "__cpp_variadic_friend=202403L");
>   	}
>         if (flag_concepts)
>           {
> --- gcc/cp/parser.cc.jj	2024-05-03 09:43:47.781511477 +0200
> +++ gcc/cp/parser.cc	2024-05-03 13:26:38.208088017 +0200
> @@ -28102,7 +28102,14 @@ cp_parser_member_declaration (cp_parser*
>       goto out;
>     /* If there is no declarator, then the decl-specifier-seq should
>        specify a type.  */

Let's mention C++26 variadic friends in this comment.  OK with that change.

> -  if (cp_lexer_next_token_is (parser->lexer, CPP_SEMICOLON))
> +  if (cp_lexer_next_token_is (parser->lexer, CPP_SEMICOLON)
> +      || (cp_parser_friend_p (&decl_specifiers)
> +	  && cxx_dialect >= cxx11
> +	  && (cp_lexer_next_token_is (parser->lexer, CPP_COMMA)
> +	      || (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
> +		  && (cp_lexer_nth_token_is (parser->lexer, 2, CPP_SEMICOLON)
> +		      || cp_lexer_nth_token_is (parser->lexer, 2,
> +						CPP_COMMA))))))
>       {
>         /* If there was no decl-specifier-seq, and the next token is a
>   	 `;', then we have something like:
> @@ -28137,44 +28144,81 @@ cp_parser_member_declaration (cp_parser*
>   	    {
>   	      /* If the `friend' keyword was present, the friend must
>   		 be introduced with a class-key.  */
> -	       if (!declares_class_or_enum && cxx_dialect < cxx11)
> -		 pedwarn (decl_spec_token_start->location, OPT_Wpedantic,
> -			  "in C++03 a class-key must be used "
> -			  "when declaring a friend");
> -	       /* In this case:
> +	      if (!declares_class_or_enum && cxx_dialect < cxx11)
> +		pedwarn (decl_spec_token_start->location, OPT_Wpedantic,
> +			 "in C++03 a class-key must be used "
> +			 "when declaring a friend");
> +	      if (!cp_lexer_next_token_is (parser->lexer, CPP_SEMICOLON)
> +		  && cxx_dialect < cxx26)
> +		pedwarn (cp_lexer_peek_token (parser->lexer)->location,
> +			 OPT_Wc__26_extensions,
> +			 "variadic friends or friend type declarations with "
> +			 "multiple types only available with "
> +			 "%<-std=c++2c%> or %<-std=gnu++2c%>");
> +	      location_t friend_loc = decl_specifiers.locations[ds_friend];
> +	      do
> +		{
> +		  /* In this case:
>   
> -		    template <typename T> struct A {
> -		      friend struct A<T>::B;
> -		    };
> +		     template <typename T> struct A {
> +		       friend struct A<T>::B;
> +		     };
>   
> -		  A<T>::B will be represented by a TYPENAME_TYPE, and
> -		  therefore not recognized by check_tag_decl.  */
> -	       if (!type)
> -		 {
> -		   type = decl_specifiers.type;
> -		   if (type && TREE_CODE (type) == TYPE_DECL)
> -		     type = TREE_TYPE (type);
> -		 }
> -	       /* Warn if an attribute cannot appear here, as per
> -		  [dcl.attr.grammar]/5.  But not when declares_class_or_enum:
> -		  we ignore attributes in elaborated-type-specifiers.  */
> -	       if (!declares_class_or_enum
> -		   && cxx11_attribute_p (decl_specifiers.attributes))
> -		 {
> -		   decl_specifiers.attributes = NULL_TREE;
> -		   if (warning_at (decl_spec_token_start->location,
> -				   OPT_Wattributes, "attribute ignored"))
> -		     inform (decl_spec_token_start->location, "an attribute "
> -			     "that appertains to a friend declaration that "
> -			     "is not a definition is ignored");
> -		 }
> -	       if (!type || !TYPE_P (type))
> -		 error_at (decl_spec_token_start->location,
> -			   "friend declaration does not name a class or "
> -			   "function");
> -	       else
> -		 make_friend_class (current_class_type, type,
> -				    /*complain=*/true);
> +		     A<T>::B will be represented by a TYPENAME_TYPE, and
> +		     therefore not recognized by check_tag_decl.  */
> +		  if (!type)
> +		    {
> +		      type = decl_specifiers.type;
> +		      if (type && TREE_CODE (type) == TYPE_DECL)
> +			type = TREE_TYPE (type);
> +		    }
> +		  /* Warn if an attribute cannot appear here, as per
> +		     [dcl.attr.grammar]/5.  But not when
> +		     declares_class_or_enum: we ignore attributes in
> +		     elaborated-type-specifiers.  */
> +		  if (!declares_class_or_enum
> +		      && cxx11_attribute_p (decl_specifiers.attributes))
> +		    {
> +		      decl_specifiers.attributes = NULL_TREE;
> +		      if (warning_at (decl_spec_token_start->location,
> +				      OPT_Wattributes, "attribute ignored"))
> +			inform (decl_spec_token_start->location, "an attribute "
> +				"that appertains to a friend declaration that "
> +				"is not a definition is ignored");
> +		    }
> +		  bool ellipsis = cp_lexer_next_token_is (parser->lexer,
> +							  CPP_ELLIPSIS);
> +		  if (ellipsis)
> +		    cp_lexer_consume_token (parser->lexer);
> +		  if (!type || !TYPE_P (type))
> +		    error_at (decl_spec_token_start->location,
> +			      "friend declaration does not name a class or "
> +			      "function");
> +		  else
> +		    {
> +		      if (ellipsis)
> +			type = make_pack_expansion (type);
> +		      if (type != error_mark_node)
> +			make_friend_class (current_class_type, type,
> +					   /*complain=*/true);
> +		    }
> +		  if (!cp_lexer_next_token_is (parser->lexer, CPP_COMMA))
> +		    break;
> +		  cp_lexer_consume_token (parser->lexer);
> +		  clear_decl_specs (&decl_specifiers);
> +		  decl_specifiers.locations[ds_friend] = friend_loc;
> +		  decl_specifiers.any_specifiers_p = true;
> +		  declares_class_or_enum = false;
> +		  cp_parser_type_specifier (parser,
> +					    CP_PARSER_FLAGS_TYPENAME_OPTIONAL,
> +					    &decl_specifiers,
> +					    /*is_declaration=*/true,
> +					    &declares_class_or_enum, NULL);
> +		  type = check_tag_decl (&decl_specifiers,
> +					 /*explicit_type_instantiation_p=*/
> +					 false);
> +		}
> +	      while (1);
>   	    }
>   	  /* If there is no TYPE, an error message will already have
>   	     been issued.  */
> --- gcc/cp/friend.cc.jj	2024-01-03 12:01:23.136483926 +0100
> +++ gcc/cp/friend.cc	2024-05-03 13:34:25.182765869 +0200
> @@ -279,7 +279,8 @@ make_friend_class (tree type, tree frien
>       }
>   
>     if (! MAYBE_CLASS_TYPE_P (friend_type)
> -      && TREE_CODE (friend_type) != TEMPLATE_TEMPLATE_PARM)
> +      && TREE_CODE (friend_type) != TEMPLATE_TEMPLATE_PARM
> +      && TREE_CODE (friend_type) != TYPE_PACK_EXPANSION)
>       {
>         /* N1791: If the type specifier in a friend declaration designates a
>   	 (possibly cv-qualified) class type, that class is declared as a
> --- gcc/cp/pt.cc.jj	2024-05-03 09:43:47.813511040 +0200
> +++ gcc/cp/pt.cc	2024-05-03 13:56:48.363582051 +0200
> @@ -12693,6 +12693,22 @@ instantiate_class_template (tree type)
>   					tf_warning_or_error, NULL_TREE);
>   		  --processing_template_decl;
>   		}
> +	      else if (PACK_EXPANSION_P (friend_type))
> +		{
> +		  friend_type = tsubst_pack_expansion (friend_type, args,
> +						       tf_warning_or_error,
> +						       NULL_TREE);
> +		  if (friend_type != error_mark_node)
> +		    {
> +		      unsigned int len = TREE_VEC_LENGTH (friend_type);
> +		      for (unsigned int idx = 0; idx < len; ++idx)
> +			if (TREE_VEC_ELT (friend_type, idx) != error_mark_node)
> +			  make_friend_class (type,
> +					     TREE_VEC_ELT (friend_type, idx),
> +					     /*complain=*/false);
> +		    }
> +		  friend_type = error_mark_node;
> +		}
>   	      else if (uses_template_parms (friend_type))
>   		/* friend class C<T>;  */
>   		friend_type = tsubst (friend_type, args,
> --- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj	2024-05-02 09:31:17.754298166 +0200
> +++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2024-05-03 14:49:10.432023113 +0200
> @@ -615,3 +615,9 @@
>   #elif __cpp_deleted_function != 202403
>   #  error "__cpp_deleted_function != 202403"
>   #endif
> +
> +#ifndef __cpp_variadic_friend
> +#  error "__cpp_variadic_friend"
> +#elif __cpp_variadic_friend != 202403
> +#  error "__cpp_variadic_friend != 202403"
> +#endif
> --- gcc/testsuite/g++.dg/cpp26/variadic-friend1.C.jj	2024-05-03 14:46:53.887873294 +0200
> +++ gcc/testsuite/g++.dg/cpp26/variadic-friend1.C	2024-05-03 14:45:50.624730486 +0200
> @@ -0,0 +1,58 @@
> +// P2893R3 - Variadic friends
> +// { dg-do compile { target c++11 } }
> +// { dg-options "" }
> +
> +template <class... Ts>
> +class A {
> +  class X {};
> +  friend Ts...;		// { dg-warning "variadic friends or friend type declarations with multiple types only available with" "" { target c++23_down } }
> +};
> +template <class... Ts, class... Us>
> +class A<A<Ts...>, A<Us...>> {
> +  class X {};
> +  friend
> +#if __cplusplus < 202002L
> +  typename
> +#endif
> +  Ts::Y..., Us...;	// { dg-warning "variadic friends or friend type declarations with multiple types only available with" "" { target c++23_down } }
> +};
> +template <typename T, typename U>
> +class B {
> +  class X {};
> +  friend T, U;		// { dg-warning "variadic friends or friend type declarations with multiple types only available with" "" { target c++23_down } }
> +};
> +template <typename T, typename U, typename... Vs>
> +class C {
> +  class X {};
> +  friend U, Vs..., T;	// { dg-warning "variadic friends or friend type declarations with multiple types only available with" "" { target c++23_down } }
> +};
> +class E;
> +class F;
> +class G;
> +class H;
> +class I;
> +class J;
> +class K;
> +class L;
> +class M;
> +class N;
> +class O;
> +class P;
> +class E : A<E, F>::X {};
> +class F : A<E, F>::X {};
> +class G : B<G, H>::X {};
> +class H : B<G, H>::X {};
> +class I : C<I, J>::X {};
> +class J : C<I, J>::X {};
> +class K : C<K, L, M, N, O>::X {};
> +class L : C<K, L, M, N, O>::X {};
> +class M : C<K, L, M, N, O>::X {};
> +class N : C<K, L, M, N, O>::X {};
> +class O : C<K, L, M, N, O>::X {};
> +struct Q { class Y : A<A<Q>, A<P, long>>::X {}; };
> +class P : A<A<Q>, A<P, long>>::X {};
> +struct R { class Y; };
> +struct S { class Y; };
> +class R::Y : A<A<R, S>, A<P, double>>::X {};
> +class S::Y : A<A<R, S>, A<P, double>>::X {};
> +A<int> a;
> 
> 	Jakub
>
  

Patch

--- gcc/c-family/c-cppbuiltin.cc.jj	2024-05-02 09:31:17.746298275 +0200
+++ gcc/c-family/c-cppbuiltin.cc	2024-05-03 14:50:08.008242950 +0200
@@ -1093,6 +1093,7 @@  c_cpp_builtins (cpp_reader *pfile)
 	  cpp_define (pfile, "__cpp_placeholder_variables=202306L");
 	  cpp_define (pfile, "__cpp_structured_bindings=202403L");
 	  cpp_define (pfile, "__cpp_deleted_function=202403L");
+	  cpp_define (pfile, "__cpp_variadic_friend=202403L");
 	}
       if (flag_concepts)
         {
--- gcc/cp/parser.cc.jj	2024-05-03 09:43:47.781511477 +0200
+++ gcc/cp/parser.cc	2024-05-03 13:26:38.208088017 +0200
@@ -28102,7 +28102,14 @@  cp_parser_member_declaration (cp_parser*
     goto out;
   /* If there is no declarator, then the decl-specifier-seq should
      specify a type.  */
-  if (cp_lexer_next_token_is (parser->lexer, CPP_SEMICOLON))
+  if (cp_lexer_next_token_is (parser->lexer, CPP_SEMICOLON)
+      || (cp_parser_friend_p (&decl_specifiers)
+	  && cxx_dialect >= cxx11
+	  && (cp_lexer_next_token_is (parser->lexer, CPP_COMMA)
+	      || (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
+		  && (cp_lexer_nth_token_is (parser->lexer, 2, CPP_SEMICOLON)
+		      || cp_lexer_nth_token_is (parser->lexer, 2,
+						CPP_COMMA))))))
     {
       /* If there was no decl-specifier-seq, and the next token is a
 	 `;', then we have something like:
@@ -28137,44 +28144,81 @@  cp_parser_member_declaration (cp_parser*
 	    {
 	      /* If the `friend' keyword was present, the friend must
 		 be introduced with a class-key.  */
-	       if (!declares_class_or_enum && cxx_dialect < cxx11)
-		 pedwarn (decl_spec_token_start->location, OPT_Wpedantic,
-			  "in C++03 a class-key must be used "
-			  "when declaring a friend");
-	       /* In this case:
+	      if (!declares_class_or_enum && cxx_dialect < cxx11)
+		pedwarn (decl_spec_token_start->location, OPT_Wpedantic,
+			 "in C++03 a class-key must be used "
+			 "when declaring a friend");
+	      if (!cp_lexer_next_token_is (parser->lexer, CPP_SEMICOLON)
+		  && cxx_dialect < cxx26)
+		pedwarn (cp_lexer_peek_token (parser->lexer)->location,
+			 OPT_Wc__26_extensions,
+			 "variadic friends or friend type declarations with "
+			 "multiple types only available with "
+			 "%<-std=c++2c%> or %<-std=gnu++2c%>");
+	      location_t friend_loc = decl_specifiers.locations[ds_friend];
+	      do
+		{
+		  /* In this case:
 
-		    template <typename T> struct A {
-		      friend struct A<T>::B;
-		    };
+		     template <typename T> struct A {
+		       friend struct A<T>::B;
+		     };
 
-		  A<T>::B will be represented by a TYPENAME_TYPE, and
-		  therefore not recognized by check_tag_decl.  */
-	       if (!type)
-		 {
-		   type = decl_specifiers.type;
-		   if (type && TREE_CODE (type) == TYPE_DECL)
-		     type = TREE_TYPE (type);
-		 }
-	       /* Warn if an attribute cannot appear here, as per
-		  [dcl.attr.grammar]/5.  But not when declares_class_or_enum:
-		  we ignore attributes in elaborated-type-specifiers.  */
-	       if (!declares_class_or_enum
-		   && cxx11_attribute_p (decl_specifiers.attributes))
-		 {
-		   decl_specifiers.attributes = NULL_TREE;
-		   if (warning_at (decl_spec_token_start->location,
-				   OPT_Wattributes, "attribute ignored"))
-		     inform (decl_spec_token_start->location, "an attribute "
-			     "that appertains to a friend declaration that "
-			     "is not a definition is ignored");
-		 }
-	       if (!type || !TYPE_P (type))
-		 error_at (decl_spec_token_start->location,
-			   "friend declaration does not name a class or "
-			   "function");
-	       else
-		 make_friend_class (current_class_type, type,
-				    /*complain=*/true);
+		     A<T>::B will be represented by a TYPENAME_TYPE, and
+		     therefore not recognized by check_tag_decl.  */
+		  if (!type)
+		    {
+		      type = decl_specifiers.type;
+		      if (type && TREE_CODE (type) == TYPE_DECL)
+			type = TREE_TYPE (type);
+		    }
+		  /* Warn if an attribute cannot appear here, as per
+		     [dcl.attr.grammar]/5.  But not when
+		     declares_class_or_enum: we ignore attributes in
+		     elaborated-type-specifiers.  */
+		  if (!declares_class_or_enum
+		      && cxx11_attribute_p (decl_specifiers.attributes))
+		    {
+		      decl_specifiers.attributes = NULL_TREE;
+		      if (warning_at (decl_spec_token_start->location,
+				      OPT_Wattributes, "attribute ignored"))
+			inform (decl_spec_token_start->location, "an attribute "
+				"that appertains to a friend declaration that "
+				"is not a definition is ignored");
+		    }
+		  bool ellipsis = cp_lexer_next_token_is (parser->lexer,
+							  CPP_ELLIPSIS);
+		  if (ellipsis)
+		    cp_lexer_consume_token (parser->lexer);
+		  if (!type || !TYPE_P (type))
+		    error_at (decl_spec_token_start->location,
+			      "friend declaration does not name a class or "
+			      "function");
+		  else
+		    {
+		      if (ellipsis)
+			type = make_pack_expansion (type);
+		      if (type != error_mark_node)
+			make_friend_class (current_class_type, type,
+					   /*complain=*/true);
+		    }
+		  if (!cp_lexer_next_token_is (parser->lexer, CPP_COMMA))
+		    break;
+		  cp_lexer_consume_token (parser->lexer);
+		  clear_decl_specs (&decl_specifiers);
+		  decl_specifiers.locations[ds_friend] = friend_loc;
+		  decl_specifiers.any_specifiers_p = true;
+		  declares_class_or_enum = false;
+		  cp_parser_type_specifier (parser,
+					    CP_PARSER_FLAGS_TYPENAME_OPTIONAL,
+					    &decl_specifiers,
+					    /*is_declaration=*/true,
+					    &declares_class_or_enum, NULL);
+		  type = check_tag_decl (&decl_specifiers,
+					 /*explicit_type_instantiation_p=*/
+					 false);
+		}
+	      while (1);
 	    }
 	  /* If there is no TYPE, an error message will already have
 	     been issued.  */
--- gcc/cp/friend.cc.jj	2024-01-03 12:01:23.136483926 +0100
+++ gcc/cp/friend.cc	2024-05-03 13:34:25.182765869 +0200
@@ -279,7 +279,8 @@  make_friend_class (tree type, tree frien
     }
 
   if (! MAYBE_CLASS_TYPE_P (friend_type)
-      && TREE_CODE (friend_type) != TEMPLATE_TEMPLATE_PARM)
+      && TREE_CODE (friend_type) != TEMPLATE_TEMPLATE_PARM
+      && TREE_CODE (friend_type) != TYPE_PACK_EXPANSION)
     {
       /* N1791: If the type specifier in a friend declaration designates a
 	 (possibly cv-qualified) class type, that class is declared as a
--- gcc/cp/pt.cc.jj	2024-05-03 09:43:47.813511040 +0200
+++ gcc/cp/pt.cc	2024-05-03 13:56:48.363582051 +0200
@@ -12693,6 +12693,22 @@  instantiate_class_template (tree type)
 					tf_warning_or_error, NULL_TREE);
 		  --processing_template_decl;
 		}
+	      else if (PACK_EXPANSION_P (friend_type))
+		{
+		  friend_type = tsubst_pack_expansion (friend_type, args,
+						       tf_warning_or_error,
+						       NULL_TREE);
+		  if (friend_type != error_mark_node)
+		    {
+		      unsigned int len = TREE_VEC_LENGTH (friend_type);
+		      for (unsigned int idx = 0; idx < len; ++idx)
+			if (TREE_VEC_ELT (friend_type, idx) != error_mark_node)
+			  make_friend_class (type,
+					     TREE_VEC_ELT (friend_type, idx),
+					     /*complain=*/false);
+		    }
+		  friend_type = error_mark_node;
+		}
 	      else if (uses_template_parms (friend_type))
 		/* friend class C<T>;  */
 		friend_type = tsubst (friend_type, args,
--- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj	2024-05-02 09:31:17.754298166 +0200
+++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2024-05-03 14:49:10.432023113 +0200
@@ -615,3 +615,9 @@ 
 #elif __cpp_deleted_function != 202403
 #  error "__cpp_deleted_function != 202403"
 #endif
+
+#ifndef __cpp_variadic_friend
+#  error "__cpp_variadic_friend"
+#elif __cpp_variadic_friend != 202403
+#  error "__cpp_variadic_friend != 202403"
+#endif
--- gcc/testsuite/g++.dg/cpp26/variadic-friend1.C.jj	2024-05-03 14:46:53.887873294 +0200
+++ gcc/testsuite/g++.dg/cpp26/variadic-friend1.C	2024-05-03 14:45:50.624730486 +0200
@@ -0,0 +1,58 @@ 
+// P2893R3 - Variadic friends
+// { dg-do compile { target c++11 } }
+// { dg-options "" }
+
+template <class... Ts>
+class A {
+  class X {};
+  friend Ts...;		// { dg-warning "variadic friends or friend type declarations with multiple types only available with" "" { target c++23_down } }
+};
+template <class... Ts, class... Us>
+class A<A<Ts...>, A<Us...>> {
+  class X {};
+  friend
+#if __cplusplus < 202002L
+  typename
+#endif
+  Ts::Y..., Us...;	// { dg-warning "variadic friends or friend type declarations with multiple types only available with" "" { target c++23_down } }
+};
+template <typename T, typename U>
+class B {
+  class X {};
+  friend T, U;		// { dg-warning "variadic friends or friend type declarations with multiple types only available with" "" { target c++23_down } }
+};
+template <typename T, typename U, typename... Vs>
+class C {
+  class X {};
+  friend U, Vs..., T;	// { dg-warning "variadic friends or friend type declarations with multiple types only available with" "" { target c++23_down } }
+};
+class E;
+class F;
+class G;
+class H;
+class I;
+class J;
+class K;
+class L;
+class M;
+class N;
+class O;
+class P;
+class E : A<E, F>::X {};
+class F : A<E, F>::X {};
+class G : B<G, H>::X {};
+class H : B<G, H>::X {};
+class I : C<I, J>::X {};
+class J : C<I, J>::X {};
+class K : C<K, L, M, N, O>::X {};
+class L : C<K, L, M, N, O>::X {};
+class M : C<K, L, M, N, O>::X {};
+class N : C<K, L, M, N, O>::X {};
+class O : C<K, L, M, N, O>::X {};
+struct Q { class Y : A<A<Q>, A<P, long>>::X {}; };
+class P : A<A<Q>, A<P, long>>::X {};
+struct R { class Y; };
+struct S { class Y; };
+class R::Y : A<A<R, S>, A<P, double>>::X {};
+class S::Y : A<A<R, S>, A<P, double>>::X {};
+A<int> a;