c++: Implement C++23 P2128R6 - Multidimensional subscript operator [PR102611]

Message ID 20211014082629.GR304296@tucnak
State Superseded
Headers
Series c++: Implement C++23 P2128R6 - Multidimensional subscript operator [PR102611] |

Commit Message

Jakub Jelinek Oct. 14, 2021, 8:26 a.m. UTC
  Hi!

The following patch implements the C++23 Multidimensional subscript operator
P2128R6 paper.
As C++20 and older only allow a single expression in between []s (albeit
for C++20 with a deprecation warning if it is a comma expression) and even
in C++23 and for the coming years I think the vast majority of subscript
expressions will still have a single expression and even in C++23 it is
quite special, as e.g. the builtin operator requires exactly one
assignment expression, the patch attempts to optimize for that case and
if possible not to slow down that common case (or use more memory for it).
So, already during parsing it differentiates between that (uses a single
index_exp tree in that case) and the new cases (zero or two+ expressions
in the list), for which it sets index_exp to NULL_TREE and uses a
releasing_vec instead similarly to how e.g. finish_call_expr uses it.
In call.c it introduces new functions build_op_subscript{,_1} which are
something in between build_new_op{,_1} and build_op_call{,_1}.
The former requires fixed number of arguments (and the patch still uses
it for the common case of subscript with exactly one index expression),
the latter handles variable number of arguments but is too CALL_EXPR specific
and handles various cases that are unnecessary for the subscript.
Right now the subscript for 0 or 2+ expressions doesn't need to deal with
builtin candidates and so is quite simple.

As discussed in the paper, for backwards compatibility, if for 2+ index
expressions build_op_subscript fails (called with tf_none) and the
expressions together form a valid comma expression (again checked with
tf_none), it is used that C++20-ish way with a pedwarn about it, but if
even that fails, build_op_subscript is called again with standard complain
flags to diagnose it in the new way.  And similarly for the builtin case.

The -Wcomma-subscript warning used to be enabled by default unless
-Wno-deprecated.  Since the C/C++98..20 behavior is no longer deprecated,
but ill-formed or changed meaning, it is now for C++23 enabled by
default regardless of -Wno-deprecated and controls the pedwarn (but not the
errors emitted if something wasn't valid before and isn't valid in C++23
either).

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

2021-10-14  Jakub Jelinek  <jakub@redhat.com>

	PR c++/102611
gcc/
	* doc/invoke.texi (-Wcomma-subscript): Document that for
	-std=c++20 the option isn't enabled by default with -Wno-deprecated
	but for -std=c++23 it is.
gcc/c-family/
	* c-opts.c (c_common_post_options): Enable -Wcomma-subscript by
	default for C++23 regardless of warn_deprecated.
	* c-cppbuiltin.c (c_cpp_builtins): Predefine
	__cpp_multidimensional_subscript=202110L for C++23.
gcc/cp/
	* cp-tree.h (build_op_subscript): Implement P2128R6
	- Multidimensional subscript operator.  Declare.
	(grok_array_decl): Remove bool argument, add vec<tree, va_gc> **
	and tsubst_flags_t arguments.
	(build_min_non_dep_op_overload): Declare another overload.
	* parser.c (cp_parser_postfix_expression): Mention C++23 syntax
	in function comment.  For C++23 parse zero or more than one
	initializer clauses in expression list, adjust grok_array_decl
	caller.
	(cp_parser_builtin_offsetof): Adjust grok_array_decl caller.
	* decl.c (grok_op_properties): For C++23 don't check number
	of arguments of operator[].
	* decl2.c (grok_array_decl): Remove decltype_p argument, add
	index_exp_list and complain arguments.  If index_exp is NULL,
	handle *index_exp_list as the subscript expression list.
	* tree.c (build_min_non_dep_op_overload): New overload.
	* call.c (build_op_subscript_1, build_op_subscript): New
	functions.
	* pt.c (tsubst_copy_and_build) <case ARRAY_REF>: If second
	operand is magic CALL_EXPR with ovl_op_identifier (ARRAY_REF)
	as CALL_EXPR_FN, tsubst CALL_EXPR arguments including expanding
	pack expressions in it and call grok_array_decl instead of
	build_x_array_ref.
	* semantics.c (handle_omp_array_sections_1): Adjust grok_array_decl
	caller.
gcc/testsuite/
	* g++.dg/cpp2a/comma1.C: Expect different diagnostics for C++23.
	* g++.dg/cpp2a/comma3.C: Likewise.
	* g++.dg/cpp2a/comma4.C: Expect diagnostics for C++23.
	* g++.dg/cpp2a/comma5.C: Expect different diagnostics for C++23.
	* g++.dg/cpp23/feat-cxx2b.C: Test __cpp_multidimensional_subscript
	predefined macro.
	* g++.dg/cpp23/subscript1.C: New test.
	* g++.dg/cpp23/subscript2.C: New test.
	* g++.dg/cpp23/subscript3.C: New test.
	* g++.dg/cpp23/subscript4.C: New test.
	* g++.dg/cpp23/subscript5.C: New test.
	* g++.dg/cpp23/subscript6.C: New test.


	Jakub
  

Comments

Jakub Jelinek Oct. 28, 2021, 2:38 p.m. UTC | #1
Hi!

On Thu, Oct 14, 2021 at 10:26:29AM +0200, Jakub Jelinek via Gcc-patches wrote:
> The following patch implements the C++23 Multidimensional subscript operator
> P2128R6 paper.

I'd like to ping this patch.

Thanks.

> 2021-10-14  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/102611
> gcc/
> 	* doc/invoke.texi (-Wcomma-subscript): Document that for
> 	-std=c++20 the option isn't enabled by default with -Wno-deprecated
> 	but for -std=c++23 it is.
> gcc/c-family/
> 	* c-opts.c (c_common_post_options): Enable -Wcomma-subscript by
> 	default for C++23 regardless of warn_deprecated.
> 	* c-cppbuiltin.c (c_cpp_builtins): Predefine
> 	__cpp_multidimensional_subscript=202110L for C++23.
> gcc/cp/
> 	* cp-tree.h (build_op_subscript): Implement P2128R6
> 	- Multidimensional subscript operator.  Declare.
> 	(grok_array_decl): Remove bool argument, add vec<tree, va_gc> **
> 	and tsubst_flags_t arguments.
> 	(build_min_non_dep_op_overload): Declare another overload.
> 	* parser.c (cp_parser_postfix_expression): Mention C++23 syntax
> 	in function comment.  For C++23 parse zero or more than one
> 	initializer clauses in expression list, adjust grok_array_decl
> 	caller.
> 	(cp_parser_builtin_offsetof): Adjust grok_array_decl caller.
> 	* decl.c (grok_op_properties): For C++23 don't check number
> 	of arguments of operator[].
> 	* decl2.c (grok_array_decl): Remove decltype_p argument, add
> 	index_exp_list and complain arguments.  If index_exp is NULL,
> 	handle *index_exp_list as the subscript expression list.
> 	* tree.c (build_min_non_dep_op_overload): New overload.
> 	* call.c (build_op_subscript_1, build_op_subscript): New
> 	functions.
> 	* pt.c (tsubst_copy_and_build) <case ARRAY_REF>: If second
> 	operand is magic CALL_EXPR with ovl_op_identifier (ARRAY_REF)
> 	as CALL_EXPR_FN, tsubst CALL_EXPR arguments including expanding
> 	pack expressions in it and call grok_array_decl instead of
> 	build_x_array_ref.
> 	* semantics.c (handle_omp_array_sections_1): Adjust grok_array_decl
> 	caller.
> gcc/testsuite/
> 	* g++.dg/cpp2a/comma1.C: Expect different diagnostics for C++23.
> 	* g++.dg/cpp2a/comma3.C: Likewise.
> 	* g++.dg/cpp2a/comma4.C: Expect diagnostics for C++23.
> 	* g++.dg/cpp2a/comma5.C: Expect different diagnostics for C++23.
> 	* g++.dg/cpp23/feat-cxx2b.C: Test __cpp_multidimensional_subscript
> 	predefined macro.
> 	* g++.dg/cpp23/subscript1.C: New test.
> 	* g++.dg/cpp23/subscript2.C: New test.
> 	* g++.dg/cpp23/subscript3.C: New test.
> 	* g++.dg/cpp23/subscript4.C: New test.
> 	* g++.dg/cpp23/subscript5.C: New test.
> 	* g++.dg/cpp23/subscript6.C: New test.

	Jakub
  
Jason Merrill Nov. 24, 2021, 3:28 a.m. UTC | #2
On 10/14/21 04:26, Jakub Jelinek wrote:
> Hi!
> 
> The following patch implements the C++23 Multidimensional subscript operator
> P2128R6 paper.
> As C++20 and older only allow a single expression in between []s (albeit
> for C++20 with a deprecation warning if it is a comma expression) and even
> in C++23 and for the coming years I think the vast majority of subscript
> expressions will still have a single expression and even in C++23 it is
> quite special, as e.g. the builtin operator requires exactly one
> assignment expression, the patch attempts to optimize for that case and
> if possible not to slow down that common case (or use more memory for it).
> So, already during parsing it differentiates between that (uses a single
> index_exp tree in that case) and the new cases (zero or two+ expressions
> in the list), for which it sets index_exp to NULL_TREE and uses a
> releasing_vec instead similarly to how e.g. finish_call_expr uses it.
>
> In call.c it introduces new functions build_op_subscript{,_1} which are
> something in between build_new_op{,_1} and build_op_call{,_1}.
> The former requires fixed number of arguments (and the patch still uses
> it for the common case of subscript with exactly one index expression),
> the latter handles variable number of arguments but is too CALL_EXPR specific
> and handles various cases that are unnecessary for the subscript.
> Right now the subscript for 0 or 2+ expressions doesn't need to deal with
> builtin candidates and so is quite simple.
> 
> As discussed in the paper, for backwards compatibility, if for 2+ index
> expressions build_op_subscript fails (called with tf_none) and the
> expressions together form a valid comma expression (again checked with
> tf_none), it is used that C++20-ish way with a pedwarn about it, but if
> even that fails, build_op_subscript is called again with standard complain
> flags to diagnose it in the new way.  And similarly for the builtin case.
> 
> The -Wcomma-subscript warning used to be enabled by default unless
> -Wno-deprecated.  Since the C/C++98..20 behavior is no longer deprecated,
> but ill-formed or changed meaning, it is now for C++23 enabled by
> default regardless of -Wno-deprecated and controls the pedwarn (but not the
> errors emitted if something wasn't valid before and isn't valid in C++23
> either).
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
> 
> 2021-10-14  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/102611
> gcc/
> 	* doc/invoke.texi (-Wcomma-subscript): Document that for
> 	-std=c++20 the option isn't enabled by default with -Wno-deprecated
> 	but for -std=c++23 it is.
> gcc/c-family/
> 	* c-opts.c (c_common_post_options): Enable -Wcomma-subscript by
> 	default for C++23 regardless of warn_deprecated.
> 	* c-cppbuiltin.c (c_cpp_builtins): Predefine
> 	__cpp_multidimensional_subscript=202110L for C++23.
> gcc/cp/
> 	* cp-tree.h (build_op_subscript): Implement P2128R6
> 	- Multidimensional subscript operator.  Declare.
> 	(grok_array_decl): Remove bool argument, add vec<tree, va_gc> **
> 	and tsubst_flags_t arguments.
> 	(build_min_non_dep_op_overload): Declare another overload.
> 	* parser.c (cp_parser_postfix_expression): Mention C++23 syntax
> 	in function comment.  For C++23 parse zero or more than one
> 	initializer clauses in expression list, adjust grok_array_decl
> 	caller.
> 	(cp_parser_builtin_offsetof): Adjust grok_array_decl caller.
> 	* decl.c (grok_op_properties): For C++23 don't check number
> 	of arguments of operator[].
> 	* decl2.c (grok_array_decl): Remove decltype_p argument, add
> 	index_exp_list and complain arguments.  If index_exp is NULL,
> 	handle *index_exp_list as the subscript expression list.
> 	* tree.c (build_min_non_dep_op_overload): New overload.
> 	* call.c (build_op_subscript_1, build_op_subscript): New
> 	functions.
> 	* pt.c (tsubst_copy_and_build) <case ARRAY_REF>: If second
> 	operand is magic CALL_EXPR with ovl_op_identifier (ARRAY_REF)
> 	as CALL_EXPR_FN, tsubst CALL_EXPR arguments including expanding
> 	pack expressions in it and call grok_array_decl instead of
> 	build_x_array_ref.
> 	* semantics.c (handle_omp_array_sections_1): Adjust grok_array_decl
> 	caller.
> gcc/testsuite/
> 	* g++.dg/cpp2a/comma1.C: Expect different diagnostics for C++23.
> 	* g++.dg/cpp2a/comma3.C: Likewise.
> 	* g++.dg/cpp2a/comma4.C: Expect diagnostics for C++23.
> 	* g++.dg/cpp2a/comma5.C: Expect different diagnostics for C++23.
> 	* g++.dg/cpp23/feat-cxx2b.C: Test __cpp_multidimensional_subscript
> 	predefined macro.
> 	* g++.dg/cpp23/subscript1.C: New test.
> 	* g++.dg/cpp23/subscript2.C: New test.
> 	* g++.dg/cpp23/subscript3.C: New test.
> 	* g++.dg/cpp23/subscript4.C: New test.
> 	* g++.dg/cpp23/subscript5.C: New test.
> 	* g++.dg/cpp23/subscript6.C: New test.
> 
> --- gcc/doc/invoke.texi.jj	2021-10-12 09:08:25.781088065 +0200
> +++ gcc/doc/invoke.texi	2021-10-13 15:26:49.284891872 +0200
> @@ -3461,19 +3461,27 @@ about ABI tags.
>   @opindex Wcomma-subscript
>   @opindex Wno-comma-subscript
>   Warn about uses of a comma expression within a subscripting expression.
> -This usage was deprecated in C++20.  However, a comma expression wrapped
> -in @code{( )} is not deprecated.  Example:
> +This usage was deprecated in C++20 and is going to be removed in C++23.
> +However, a comma expression wrapped in @code{( )} is not deprecated.  Example:
>   
>   @smallexample
>   @group
>   void f(int *a, int b, int c) @{
> -    a[b,c];     // deprecated
> +    a[b,c];     // deprecated in C++20, invalid in C++23
>       a[(b,c)];   // OK
>   @}
>   @end group
>   @end smallexample
>   
> -Enabled by default with @option{-std=c++20}.
> +In C++23 it is valid to have comma separated expressions in a subscript
> +when an overloaded subscript operator is found and supports the right
> +number and types of arguments.  G++ will accept the formerly valid syntax
> +for code that is not valid in C++23 but used to be valid but deprecated
> +in C++20 with a pedantic warning that can be disabled with
> +@option{-Wno-comma-subscript}.
> +
> +Enabled by default with @option{-std=c++20} unless @option{-Wno-deprecated},
> +and with @option{-std=c++23} regardless of @option{-Wno-deprecated}.
>   
>   @item -Wctad-maybe-unsupported @r{(C++ and Objective-C++ only)}
>   @opindex Wctad-maybe-unsupported
> --- gcc/c-family/c-opts.c.jj	2021-10-09 10:07:51.697707634 +0200
> +++ gcc/c-family/c-opts.c	2021-10-13 15:18:50.251789524 +0200
> @@ -946,7 +946,8 @@ c_common_post_options (const char **pfil
>     /* -Wcomma-subscript is enabled by default in C++20.  */
>     SET_OPTION_IF_UNSET (&global_options, &global_options_set,
>   		       warn_comma_subscript,
> -		       cxx_dialect >= cxx20 && warn_deprecated);
> +		       cxx_dialect >= cxx23
> +		       || (cxx_dialect == cxx20 && warn_deprecated));
>   
>     /* -Wvolatile is enabled by default in C++20.  */
>     SET_OPTION_IF_UNSET (&global_options, &global_options_set, warn_volatile,
> --- gcc/c-family/c-cppbuiltin.c.jj	2021-10-06 10:28:16.228726784 +0200
> +++ gcc/c-family/c-cppbuiltin.c	2021-10-13 16:37:53.030557910 +0200
> @@ -1073,6 +1073,7 @@ c_cpp_builtins (cpp_reader *pfile)
>   	  cpp_define (pfile, "__cpp_size_t_suffix=202011L");
>   	  cpp_define (pfile, "__cpp_if_consteval=202106L");
>   	  cpp_define (pfile, "__cpp_constexpr=202110L");
> +	  cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
>   	}
>         if (flag_concepts)
>           {
> --- gcc/cp/cp-tree.h.jj	2021-10-06 10:11:38.296564499 +0200
> +++ gcc/cp/cp-tree.h	2021-10-13 20:17:57.050035862 +0200
> @@ -6465,6 +6465,9 @@ inline tree build_new_op (const op_locat
>   }
>   extern tree build_op_call			(tree, vec<tree, va_gc> **,
>   						 tsubst_flags_t);
> +extern tree build_op_subscript			(const op_location_t &, tree,
> +						 vec<tree, va_gc> **, tree *,
> +						 tsubst_flags_t);
>   extern bool aligned_allocation_fn_p		(tree);
>   extern tree destroying_delete_p			(tree);
>   extern bool usual_deallocation_fn_p		(tree);
> @@ -6806,7 +6809,8 @@ extern void maybe_make_one_only			(tree)
>   extern bool vague_linkage_p			(tree);
>   extern void grokclassfn				(tree, tree,
>   						 enum overload_flags);
> -extern tree grok_array_decl			(location_t, tree, tree, bool);
> +extern tree grok_array_decl			(location_t, tree, tree,
> +						 vec<tree, va_gc> **, tsubst_flags_t);
>   extern tree delete_sanity			(location_t, tree, tree, bool,
>   						 int, tsubst_flags_t);
>   extern tree check_classfn			(tree, tree, tree);
> @@ -7703,6 +7707,8 @@ extern tree build_min_nt_loc			(location
>   						 ...);
>   extern tree build_min_non_dep			(enum tree_code, tree, ...);
>   extern tree build_min_non_dep_op_overload	(enum tree_code, tree, tree, ...);
> +extern tree build_min_non_dep_op_overload	(tree, tree, tree,
> +						 vec<tree, va_gc> *);
>   extern tree build_min_nt_call_vec (tree, vec<tree, va_gc> *);
>   extern tree build_min_non_dep_call_vec		(tree, tree, vec<tree, va_gc> *);
>   extern vec<tree, va_gc>* vec_copy_and_insert    (vec<tree, va_gc>*, tree, unsigned);
> --- gcc/cp/parser.c.jj	2021-10-12 09:08:08.689334252 +0200
> +++ gcc/cp/parser.c	2021-10-13 19:28:02.970059048 +0200
> @@ -7885,6 +7885,7 @@ cp_parser_postfix_expression (cp_parser
>   
>        postfix-expression [ expression ]
>        postfix-expression [ braced-init-list ] (C++11)
> +     postfix-expression [ expression-list[opt] ] (C++23)
>   
>      FOR_OFFSETOF is set if we're being called in that context, which
>      changes how we deal with integer constant expressions.  */
> @@ -7896,6 +7897,7 @@ cp_parser_postfix_open_square_expression
>   					  bool decltype_p)
>   {
>     tree index = NULL_TREE;
> +  releasing_vec expression_list = NULL;
>     location_t loc = cp_lexer_peek_token (parser->lexer)->location;
>     bool saved_greater_than_is_operator_p;
>   
> @@ -7917,7 +7919,63 @@ cp_parser_postfix_open_square_expression
>       index = cp_parser_constant_expression (parser);
>     else
>       {
> -      if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE))
> +      if (cxx_dialect >= cxx23
> +	  && cp_lexer_next_token_is (parser->lexer, CPP_CLOSE_SQUARE))
> +	*&expression_list = make_tree_vector ();
> +      else if (cxx_dialect >= cxx23)
> +	{
> +	  while (true)
> +	    {
> +	      cp_expr expr (NULL_TREE);
> +	      /* Parse the next assignment-expression.  */
> +	      if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE))
> +		{
> +		  /* A braced-init-list.  */
> +		  bool expr_nonconst_p;
> +		  cp_lexer_set_source_position (parser->lexer);
> +		  expr = cp_parser_braced_list (parser, &expr_nonconst_p);
> +		}
> +	      else
> +		expr = cp_parser_assignment_expression (parser);
> +
> +	      /* If we have an ellipsis, then this is an expression
> +		 expansion.  */
> +	      if (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS))
> +		{
> +		  /* Consume the `...'.  */
> +		  cp_lexer_consume_token (parser->lexer);
> +		  /* Build the argument pack.  */
> +		  expr = make_pack_expansion (expr);
> +		}
> +
> +	      if (expr == error_mark_node)
> +		index = error_mark_node;
> +	      else if (expression_list.get () == NULL
> +		       && !PACK_EXPANSION_P (expr.get_value ()))
> +		index = expr.get_value ();
> +	      else
> +		vec_safe_push (expression_list, expr.get_value ());
> +
> +	      /* If the next token isn't a `,', then we are done.  */
> +	      if (cp_lexer_next_token_is_not (parser->lexer, CPP_COMMA))
> +		break;
> +
> +	      if (expression_list.get () == NULL && index != error_mark_node)
> +		{
> +		  *&expression_list = make_tree_vector_single (index);
> +		  index = NULL_TREE;
> +		}
> +
> +	      /* Otherwise, consume the `,' and keep going.  */
> +	      cp_lexer_consume_token (parser->lexer);
> +	    }

Let's share this loop with cp_parser_parenthesized_expression_list.

> +	  if (expression_list.get () && index == error_mark_node)
> +	    {
> +	      release_tree_vector (*&expression_list);
> +	      *&expression_list = NULL;

This should probably become a release() method in releasing_vec.

> +	    }
> +	}
> +      else if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE))
>   	{
>   	  bool expr_nonconst_p;
>   	  cp_lexer_set_source_position (parser->lexer);
> @@ -7937,7 +7995,9 @@ cp_parser_postfix_open_square_expression
>   
>     /* Build the ARRAY_REF.  */
>     postfix_expression = grok_array_decl (loc, postfix_expression,
> -					index, decltype_p);
> +					index, &expression_list,
> +					tf_warning_or_error
> +					| (decltype_p ? tf_decltype : 0));
>   
>     /* When not doing offsetof, array references are not permitted in
>        constant-expressions.  */
> @@ -10607,8 +10667,8 @@ cp_parser_builtin_offsetof (cp_parser *p
>   
>   	case CPP_DEREF:
>   	  /* offsetof-member-designator "->" identifier */
> -	  expr = grok_array_decl (token->location, expr,
> -				  integer_zero_node, false);
> +	  expr = grok_array_decl (token->location, expr, integer_zero_node,
> +				  NULL, tf_warning_or_error);
>   	  /* FALLTHRU */
>   
>   	case CPP_DOT:
> --- gcc/cp/decl.c.jj	2021-10-12 09:08:08.640334958 +0200
> +++ gcc/cp/decl.c	2021-10-13 11:36:19.587853088 +0200
> @@ -15151,6 +15151,8 @@ grok_op_properties (tree decl, bool comp
>       case OVL_OP_FLAG_BINARY:
>         if (arity != 2)
>   	{
> +	  if (operator_code == ARRAY_REF && cxx_dialect >= cxx23)
> +	    break;
>   	  error_at (loc,
>   		    methodp
>   		    ? G_("%qD must have exactly one argument")
> --- gcc/cp/decl2.c.jj	2021-09-09 10:40:26.077175937 +0200
> +++ gcc/cp/decl2.c	2021-10-13 20:20:26.892879333 +0200
> @@ -363,16 +363,20 @@ grokclassfn (tree ctype, tree function,
>   }
>   
>   /* Create an ARRAY_REF, checking for the user doing things backwards
> -   along the way.  DECLTYPE_P is for N3276, as in the parser.  */
> +   along the way.
> +   If INDEX_EXP is non-NULL, then that is the index expression,
> +   otherwise INDEX_EXP_LIST is the list of index expressions.  */
>   
>   tree
>   grok_array_decl (location_t loc, tree array_expr, tree index_exp,
> -		 bool decltype_p)
> +		 vec<tree, va_gc> **index_exp_list, tsubst_flags_t complain)
>   {
>     tree type;
>     tree expr;
>     tree orig_array_expr = array_expr;
>     tree orig_index_exp = index_exp;
> +  vec<tree, va_gc> *orig_index_exp_list
> +    = index_exp_list ? *index_exp_list : NULL;
>     tree overload = NULL_TREE;
>   
>     if (error_operand_p (array_expr) || error_operand_p (index_exp))
> @@ -381,11 +385,23 @@ grok_array_decl (location_t loc, tree ar
>     if (processing_template_decl)
>       {
>         if (type_dependent_expression_p (array_expr)
> -	  || type_dependent_expression_p (index_exp))
> -	return build_min_nt_loc (loc, ARRAY_REF, array_expr, index_exp,
> -				 NULL_TREE, NULL_TREE);
> +	  || (index_exp ? type_dependent_expression_p (index_exp)
> +			: any_type_dependent_arguments_p (*index_exp_list)))
> +	{
> +	  if (index_exp == NULL)
> +	    index_exp = build_min_nt_call_vec (ovl_op_identifier (ARRAY_REF),
> +					       *index_exp_list);
> +	  return build_min_nt_loc (loc, ARRAY_REF, array_expr, index_exp,
> +				   NULL_TREE, NULL_TREE);
> +	}
>         array_expr = build_non_dependent_expr (array_expr);
> -      index_exp = build_non_dependent_expr (index_exp);
> +      if (index_exp)
> +	index_exp = build_non_dependent_expr (index_exp);
> +      else
> +	{
> +	  orig_index_exp_list = make_tree_vector_copy (*index_exp_list);
> +	  make_args_non_dependent (*index_exp_list);
> +	}
>       }
>   
>     type = TREE_TYPE (array_expr);
> @@ -393,13 +409,54 @@ grok_array_decl (location_t loc, tree ar
>     type = non_reference (type);
>   
>     /* If they have an `operator[]', use that.  */
> -  if (MAYBE_CLASS_TYPE_P (type) || MAYBE_CLASS_TYPE_P (TREE_TYPE (index_exp)))
> +  if (MAYBE_CLASS_TYPE_P (type)
> +      || (index_exp && MAYBE_CLASS_TYPE_P (TREE_TYPE (index_exp)))
> +      || (index_exp == NULL_TREE
> +	  && !(*index_exp_list)->is_empty ()
> +	  && MAYBE_CLASS_TYPE_P (TREE_TYPE ((*index_exp_list)->last ()))))
>       {
> -      tsubst_flags_t complain = tf_warning_or_error;
> -      if (decltype_p)
> -	complain |= tf_decltype;
> -      expr = build_new_op (loc, ARRAY_REF, LOOKUP_NORMAL, array_expr,
> -			   index_exp, NULL_TREE, &overload, complain);
> +      if (index_exp)
> +	expr = build_new_op (loc, ARRAY_REF, LOOKUP_NORMAL, array_expr,
> +			     index_exp, NULL_TREE, &overload, complain);
> +      else if ((*index_exp_list)->is_empty ())
> +	expr = build_op_subscript (loc, array_expr, index_exp_list, &overload,
> +				   complain);
> +      else
> +	{
> +	  expr = build_op_subscript (loc, array_expr, index_exp_list,
> +				     &overload, complain & tf_decltype);
> +	  if (expr == error_mark_node)
> +	    {
> +	      tree idx = NULL_TREE;
> +	      unsigned int i;
> +	      tree e;
> +	      FOR_EACH_VEC_SAFE_ELT (*index_exp_list, i, e)

This is build_x_compound_expr_from_vec.

> +		if (idx == NULL_TREE)
> +		  idx = e;
> +		else
> +		  {
> +		    idx = build_x_compound_expr (loc, idx, e, tf_none);
> +		    if (idx == error_mark_node)
> +		      break;
> +		  }
> +	      if (idx != error_mark_node)
> +		expr = build_new_op (loc, ARRAY_REF, LOOKUP_NORMAL, array_expr,
> +				     idx, NULL_TREE, &overload,
> +				     complain & tf_decltype);
> +	      if (expr == error_mark_node)
> +		{
> +		  overload = NULL_TREE;
> +		  expr = build_op_subscript (loc, array_expr, index_exp_list,
> +					     &overload, complain);
> +		}
> +	      else
> +		/* If it would be valid albeit deprecated expression in C++20,
> +		   just pedwarn on it and treat it as if wrapped in ().  */
> +		pedwarn (loc, OPT_Wcomma_subscript,
> +			 "top-level comma expression in array subscript "
> +			 "changed meaning in C++23");
> +	    }
> +	}
>       }
>     else
>       {
> @@ -415,6 +472,41 @@ grok_array_decl (location_t loc, tree ar
>         else
>   	p1 = build_expr_type_conversion (WANT_POINTER, array_expr, false);
>   
> +      if (index_exp == NULL_TREE)
> +	{
> +	  if ((*index_exp_list)->is_empty ())
> +	    {
> +	      error_at (loc, "built-in subscript operator without expression "
> +			     "list");
> +	      return error_mark_node;
> +	    }
> +	  tree idx = NULL_TREE;
> +	  unsigned int i;
> +	  tree e;
> +	  FOR_EACH_VEC_SAFE_ELT (*index_exp_list, i, e)
> +	    if (idx == NULL_TREE)
> +	      idx = e;
> +	    else
> +	      {
> +		idx = build_x_compound_expr (loc, idx, e, tf_none);
> +		if (idx == error_mark_node)
> +		  break;
> +	      }

Likewise.

> +	  if (idx != error_mark_node)
> +	    /* If it would be valid albeit deprecated expression in C++20,
> +	       just pedwarn on it and treat it as if wrapped in ().  */
> +	    pedwarn (loc, OPT_Wcomma_subscript,
> +		     "top-level comma expression in array subscript "
> +		     "changed meaning in C++23");
> +	  else
> +	    {
> +	      error_at (loc, "built-in subscript operator with more than one "
> +			     "expression in expression list");
> +	      return error_mark_node;
> +	    }
> +	  index_exp = idx;
> +	}
> +
>         if (TREE_CODE (TREE_TYPE (index_exp)) == ARRAY_TYPE)
>   	p2 = index_exp;
>         else
> @@ -457,11 +549,30 @@ grok_array_decl (location_t loc, tree ar
>     if (processing_template_decl && expr != error_mark_node)
>       {
>         if (overload != NULL_TREE)
> -	return (build_min_non_dep_op_overload
> -		(ARRAY_REF, expr, overload, orig_array_expr, orig_index_exp));
> +	{
> +	  if (orig_index_exp == NULL_TREE)
> +	    {
> +	      expr = build_min_non_dep_op_overload (expr, overload,
> +						    orig_array_expr,
> +						    orig_index_exp_list);
> +	      release_tree_vector (orig_index_exp_list);
> +	      return expr;
> +	    }
> +	  return build_min_non_dep_op_overload (ARRAY_REF, expr, overload,
> +						orig_array_expr,
> +						orig_index_exp);
> +	}
> +
> +      if (orig_index_exp == NULL_TREE)
> +	{
> +	  orig_index_exp
> +	    = build_min_nt_call_vec (ovl_op_identifier (ARRAY_REF),
> +				     orig_index_exp_list);
> +	  release_tree_vector (orig_index_exp_list);
> +	}
>   
> -      return build_min_non_dep (ARRAY_REF, expr, orig_array_expr, orig_index_exp,
> -				NULL_TREE, NULL_TREE);
> +      return build_min_non_dep (ARRAY_REF, expr, orig_array_expr,
> +				orig_index_exp, NULL_TREE, NULL_TREE);
>       }
>     return expr;
>   }
> --- gcc/cp/tree.c.jj	2021-10-05 22:28:31.072246826 +0200
> +++ gcc/cp/tree.c	2021-10-13 20:22:08.407418346 +0200
> @@ -3671,13 +3671,42 @@ build_min_non_dep_op_overload (enum tree
>   	}
>       }
>     else
> -   gcc_unreachable ();
> +    gcc_unreachable ();
>   
>     va_end (p);
>     call = build_min_non_dep_call_vec (non_dep, fn, args);
>   
>     tree call_expr = extract_call_expr (call);
>     KOENIG_LOOKUP_P (call_expr) = KOENIG_LOOKUP_P (non_dep);
> +  CALL_EXPR_OPERATOR_SYNTAX (call_expr) = true;
> +  CALL_EXPR_ORDERED_ARGS (call_expr) = CALL_EXPR_ORDERED_ARGS (non_dep);
> +  CALL_EXPR_REVERSE_ARGS (call_expr) = CALL_EXPR_REVERSE_ARGS (non_dep);
> +
> +  return call;
> +}
> +
> +/* Similar to above build_min_non_dep_op_overload, but arguments
> +   are taken from ARGS vector.  */
> +
> +tree
> +build_min_non_dep_op_overload (tree non_dep, tree overload, tree object,
> +			       vec<tree, va_gc> *args)
> +{
> +  non_dep = extract_call_expr (non_dep);
> +
> +  unsigned int nargs = call_expr_nargs (non_dep);
> +  gcc_assert (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE);
> +  tree binfo = TYPE_BINFO (TREE_TYPE (object));
> +  tree method = build_baselink (binfo, binfo, overload, NULL_TREE);
> +  tree fn = build_min (COMPONENT_REF, TREE_TYPE (overload),
> +		       object, method, NULL_TREE);
> +  nargs--;
> +  gcc_assert (vec_safe_length (args) == nargs);
> +
> +  tree call = build_min_non_dep_call_vec (non_dep, fn, args);
> +
> +  tree call_expr = extract_call_expr (call);
> +  KOENIG_LOOKUP_P (call_expr) = KOENIG_LOOKUP_P (non_dep);
>     CALL_EXPR_OPERATOR_SYNTAX (call_expr) = true;
>     CALL_EXPR_ORDERED_ARGS (call_expr) = CALL_EXPR_ORDERED_ARGS (non_dep);
>     CALL_EXPR_REVERSE_ARGS (call_expr) = CALL_EXPR_REVERSE_ARGS (non_dep);
> --- gcc/cp/call.c.jj	2021-09-16 10:51:02.246976887 +0200
> +++ gcc/cp/call.c	2021-10-13 14:20:05.939497079 +0200
> @@ -6973,6 +6973,129 @@ build_new_op (const op_location_t &loc,
>     return ret;
>   }
>   
> +/* Build a new call to operator[].  This may change ARGS.  */
> +
> +static tree
> +build_op_subscript_1 (const op_location_t &loc, tree obj,
> +		      vec<tree, va_gc> **args, tree *overload,
> +		      tsubst_flags_t complain)
> +{
> +  struct z_candidate *candidates = 0, *cand;
> +  tree fns, first_mem_arg = NULL_TREE;
> +  bool any_viable_p;
> +  tree result = NULL_TREE;
> +  void *p;
> +
> +  obj = mark_lvalue_use (obj);
> +
> +  if (error_operand_p (obj))
> +    return error_mark_node;
> +
> +  tree type = TREE_TYPE (obj);
> +
> +  obj = prep_operand (obj);
> +
> +  if (TYPE_BINFO (type))
> +    {
> +      fns = lookup_fnfields (TYPE_BINFO (type), ovl_op_identifier (ARRAY_REF),
> +			     1, complain);
> +      if (fns == error_mark_node)
> +	return error_mark_node;
> +    }
> +  else
> +    fns = NULL_TREE;
> +
> +  if (args != NULL && *args != NULL)
> +    {
> +      *args = resolve_args (*args, complain);
> +      if (*args == NULL)
> +	return error_mark_node;
> +    }
> +
> +  /* Get the high-water mark for the CONVERSION_OBSTACK.  */
> +  p = conversion_obstack_alloc (0);
> +
> +  if (fns)
> +    {
> +      first_mem_arg = obj;
> +
> +      add_candidates (BASELINK_FUNCTIONS (fns),
> +		      first_mem_arg, *args, NULL_TREE,
> +		      NULL_TREE, false,
> +		      BASELINK_BINFO (fns), BASELINK_ACCESS_BINFO (fns),
> +		      LOOKUP_NORMAL, &candidates, complain);
> +    }
> +
> +  /* Be strict here because if we choose a bad conversion candidate, the
> +     errors we get won't mention the call context.  */
> +  candidates = splice_viable (candidates, true, &any_viable_p);
> +  if (!any_viable_p)
> +    {
> +      if (complain & tf_error)
> +	{
> +	  auto_diagnostic_group d;
> +	  error ("no match for call to %<%T::operator[] (%A)%>",
> +		 TREE_TYPE (obj), build_tree_list_vec (*args));
> +	  print_z_candidates (loc, candidates);
> +	}
> +      result = error_mark_node;
> +    }
> +  else
> +    {
> +      cand = tourney (candidates, complain);
> +      if (cand == 0)
> +	{
> +	  if (complain & tf_error)
> +	    {
> +	      auto_diagnostic_group d;
> +	      error ("call of %<%T::operator[] (%A)%> is ambiguous",
> +		     TREE_TYPE (obj), build_tree_list_vec (*args));
> +	      print_z_candidates (loc, candidates);
> +	    }
> +	  result = error_mark_node;
> +	}
> +      else if (TREE_CODE (cand->fn) == FUNCTION_DECL
> +	       && DECL_OVERLOADED_OPERATOR_P (cand->fn)
> +	       && DECL_OVERLOADED_OPERATOR_IS (cand->fn, ARRAY_REF))
> +	{
> +	  if (overload)
> +	    *overload = cand->fn;
> +	  result = build_over_call (cand, LOOKUP_NORMAL, complain);
> +	  if (trivial_fn_p (cand->fn) || DECL_IMMEDIATE_FUNCTION_P (cand->fn))
> +	    /* There won't be a CALL_EXPR.  */;
> +	  else if (result && result != error_mark_node)
> +	    {
> +	      tree call = extract_call_expr (result);
> +	      CALL_EXPR_OPERATOR_SYNTAX (call) = true;
> +
> +	      /* Specify evaluation order as per P0145R2.  */
> +	      CALL_EXPR_ORDERED_ARGS (call) = op_is_ordered (ARRAY_REF) == 1;
> +	    }
> +	}
> +      else
> +	gcc_unreachable ();
> +    }
> +
> +  /* Free all the conversions we allocated.  */
> +  obstack_free (&conversion_obstack, p);
> +
> +  return result;
> +}
> +
> +/* Wrapper for above.  */

I just applied my auto_cond_timevar patch, so you can use that instead 
of the wrapper.

> +tree
> +build_op_subscript (const op_location_t &loc, tree obj,
> +		    vec<tree, va_gc> **args, tree *overload,
> +		    tsubst_flags_t complain)
> +{
> +  tree ret;
> +  bool subtime = timevar_cond_start (TV_OVERLOAD);
> +  ret = build_op_subscript_1 (loc, obj, args, overload, complain);
> +  timevar_cond_stop (TV_OVERLOAD, subtime);
> +  return ret;
> +}
> +
>   /* CALL was returned by some call-building function; extract the actual
>      CALL_EXPR from any bits that have been tacked on, e.g. by
>      convert_from_reference.  */
> --- gcc/cp/pt.c.jj	2021-10-08 10:52:47.060532186 +0200
> +++ gcc/cp/pt.c	2021-10-13 19:29:47.705556070 +0200
> @@ -20038,6 +20038,56 @@ tsubst_copy_and_build (tree t,
>       case ARRAY_REF:
>         op1 = tsubst_non_call_postfix_expression (TREE_OPERAND (t, 0),
>   						args, complain, in_decl);
> +      if (TREE_CODE (TREE_OPERAND (t, 1)) == CALL_EXPR
> +	  && (CALL_EXPR_FN (TREE_OPERAND (t, 1))
> +	      == ovl_op_identifier (ARRAY_REF)))
> +	{
> +	  tree c = TREE_OPERAND (t, 1);
> +	  unsigned nargs = call_expr_nargs (c), i;
> +	  releasing_vec index_exp_list;
> +	  for (i = 0; i < nargs; ++i)
> +	    {
> +	      tree arg = CALL_EXPR_ARG (c, i);
> +
> +	      if (!PACK_EXPANSION_P (arg))
> +		vec_safe_push (index_exp_list, RECUR (arg));
> +	      else
> +		{
> +		  /* Expand the pack expansion and push each entry onto
> +		     INDEX_EXP_LIST.  */
> +		  arg = tsubst_pack_expansion (arg, args, complain, in_decl);
> +		  if (TREE_CODE (arg) == TREE_VEC)
> +		    {
> +		      unsigned int len, j;
> +
> +		      len = TREE_VEC_LENGTH (arg);
> +		      for (j = 0; j < len; ++j)
> +			{
> +			  tree value = TREE_VEC_ELT (arg, j);
> +			  if (value != NULL_TREE)
> +			    value = convert_from_reference (value);
> +			  vec_safe_push (index_exp_list, value);
> +			}
> +		    }
> +		  else
> +		    {
> +		      /* A partial substitution.  Add one entry.  */
> +		      vec_safe_push (index_exp_list, arg);
> +		    }
> +		}
> +	    }

Let's share this code with CALL_EXPR instead of duplicating it.

> +	  tree r;
> +	  if (vec_safe_length (index_exp_list) == 1
> +	      && !PACK_EXPANSION_P (index_exp_list[0]))
> +	    r = grok_array_decl (EXPR_LOCATION (t), op1,
> +				 index_exp_list[0], NULL,
> +				 complain | decltype_flag);
> +	  else
> +	    r = grok_array_decl (EXPR_LOCATION (t), op1,
> +				 NULL_TREE, &index_exp_list,
> +				 complain | decltype_flag);
> +	  RETURN (r);
> +	}
>         RETURN (build_x_array_ref (EXPR_LOCATION (t), op1,
>   				 RECUR (TREE_OPERAND (t, 1)),
>   				 complain|decltype_flag));
> --- gcc/cp/semantics.c.jj	2021-09-22 09:29:01.061813863 +0200
> +++ gcc/cp/semantics.c	2021-10-13 17:51:13.294341545 +0200
> @@ -5362,7 +5362,8 @@ handle_omp_array_sections_1 (tree c, tre
>   		      OMP_CLAUSE_CODE (c) == OMP_CLAUSE_REDUCTION
>   		      || OMP_CLAUSE_CODE (c) == OMP_CLAUSE_IN_REDUCTION
>   		      || OMP_CLAUSE_CODE (c) == OMP_CLAUSE_TASK_REDUCTION);
> -  ret = grok_array_decl (OMP_CLAUSE_LOCATION (c), ret, low_bound, false);
> +  ret = grok_array_decl (OMP_CLAUSE_LOCATION (c), ret, low_bound, NULL,
> +			 tf_warning_or_error);
>     return ret;
>   }
>   
> --- gcc/testsuite/g++.dg/cpp2a/comma1.C.jj	2020-07-28 15:39:09.998756365 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/comma1.C	2021-10-13 15:45:37.670653141 +0200
> @@ -8,19 +8,24 @@ struct S {
>   void
>   fn (int *a, int b, int c)
>   {
> -  a[b,c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20 } }
> +  a[b,c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_only } }
> +	  // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[(b,c)];
>   
> -  a[(void) b, c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20 } }
> +  a[(void) b, c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_only } }
> +		  // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[((void) b, c)];
>   
> -  a[(void) b, (void) c, (void) b, b]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20 } }
> +  a[(void) b, (void) c, (void) b, b]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_only } }
> +				      // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[((void) b, (void) c, (void) b, b)];
>   
> -  a[S(), 10]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20 } }
> +  a[S(), 10]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_only } }
> +	      // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[(S(), 10)];
>   
>     a[int{(1,2)}];
> -  a[int{(1,2)}, int{}]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20 } }
> +  a[int{(1,2)}, int{}]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_only } }
> +			// { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[(int{(1,2)}, int{})];
>   }
> --- gcc/testsuite/g++.dg/cpp2a/comma3.C.jj	2020-01-12 11:54:37.135402516 +0100
> +++ gcc/testsuite/g++.dg/cpp2a/comma3.C	2021-10-13 15:30:35.134639835 +0200
> @@ -9,19 +9,24 @@ struct S {
>   void
>   fn (int *a, int b, int c)
>   {
> -  a[b,c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
> +  a[b,c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
> +	  // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[(b,c)];
>   
> -  a[(void) b, c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
> +  a[(void) b, c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
> +		  // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[((void) b, c)];
>   
> -  a[(void) b, (void) c, (void) b, b]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
> +  a[(void) b, (void) c, (void) b, b]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
> +				      // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[((void) b, (void) c, (void) b, b)];
>   
> -  a[S(), 10]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
> +  a[S(), 10]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
> +	      // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[(S(), 10)];
>   
>     a[int{(1,2)}];
> -  a[int{(1,2)}, int{}]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
> +  a[int{(1,2)}, int{}]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
> +			// { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[(int{(1,2)}, int{})];
>   }
> --- gcc/testsuite/g++.dg/cpp2a/comma4.C.jj	2020-07-28 15:39:09.998756365 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/comma4.C	2021-10-13 15:33:52.944792025 +0200
> @@ -10,18 +10,23 @@ void
>   fn (int *a, int b, int c)
>   {
>     a[b,c]; // { dg-bogus "top-level comma expression in array subscript is deprecated" }
> +	  // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[(b,c)];
>   
>     a[(void) b, c]; // { dg-bogus "top-level comma expression in array subscript is deprecated" }
> +		  // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[((void) b, c)];
>   
>     a[(void) b, (void) c, (void) b, b]; // { dg-bogus "top-level comma expression in array subscript is deprecated" }
> +				      // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[((void) b, (void) c, (void) b, b)];
>   
>     a[S(), 10]; // { dg-bogus "top-level comma expression in array subscript is deprecated" }
> +	      // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[(S(), 10)];
>   
>     a[int{(1,2)}];
>     a[int{(1,2)}, int{}]; // { dg-bogus "top-level comma expression in array subscript is deprecated" }
> +			// { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[(int{(1,2)}, int{})];
>   }
> --- gcc/testsuite/g++.dg/cpp2a/comma5.C.jj	2020-07-28 15:39:09.998756365 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/comma5.C	2021-10-13 15:46:11.147171517 +0200
> @@ -8,14 +8,20 @@ void
>   fn (int *a, int b, int c)
>   {
>     a[foo<int, int>(1, 2)];
> -  a[foo<int, int>(1, 2), foo<int, int>(3, 4)]; // { dg-warning "24:top-level comma expression in array subscript is deprecated" }
> +  a[foo<int, int>(1, 2), foo<int, int>(3, 4)]; // { dg-warning "24:top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
> +					       // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>   
> -  a[b < c, b < c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
> -  a[b < c, b > c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
> -  a[b > c, b > c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
> -  a[b > c, b < c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
> +  a[b < c, b < c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
> +		   // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
> +  a[b < c, b > c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
> +		   // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
> +  a[b > c, b > c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
> +		   // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
> +  a[b > c, b < c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
> +		   // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[(b < c, b < c)];
>     a[(b < c, b > c)];
> -  a[b << c, b << c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
> +  a[b << c, b << c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
> +		     // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
>     a[(b << c, b << c)];
>   }
> --- gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C.jj	2021-10-06 10:28:16.256726395 +0200
> +++ gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C	2021-10-13 16:40:07.980616965 +0200
> @@ -551,3 +551,9 @@
>   #elif __cpp_if_consteval != 202106
>   #  error "__cpp_if_consteval != 202106"
>   #endif
> +
> +#ifndef __cpp_multidimensional_subscript
> +#  error "__cpp_multidimensional_subscript"
> +#elif __cpp_multidimensional_subscript != 202110
> +#  error "__cpp_multidimensional_subscript != 202110"
> +#endif
> --- gcc/testsuite/g++.dg/cpp23/subscript1.C.jj	2021-10-13 16:04:24.030452004 +0200
> +++ gcc/testsuite/g++.dg/cpp23/subscript1.C	2021-10-13 16:10:19.161344421 +0200
> @@ -0,0 +1,55 @@
> +// P2128R6
> +// { dg-do run }
> +// { dg-options "-std=c++23" }
> +
> +extern "C" void abort ();
> +
> +struct S
> +{
> +  constexpr S () : a {} {};
> +  constexpr S (int x, int y, int z) : a {x, y, z} {};
> +  constexpr int &operator[] () { return a[0]; }
> +  constexpr int &operator[] (int x) { return a[x]; }
> +  constexpr int &operator[] (int x, long y) { return a[x + y * 8]; }
> +  int a[64];
> +};
> +
> +struct T
> +{
> +  operator int () { return 42; };
> +};
> +
> +int buf[64];
> +
> +struct U
> +{
> +  operator int * () { return buf; }
> +};
> +
> +static_assert (S ()[1] == 0);
> +static_assert (S (1, 2, 42)[2] == 42);
> +static_assert (S ()[3, 4] == 0);
> +static_assert (S (1, 43, 2)[1, 0] == 43);
> +static_assert (S ()[] == 0);
> +static_assert (S (44, 1, 2)[] == 44);
> +
> +int
> +main ()
> +{
> +  S s;
> +  for (int i = 0; i < 64; i++)
> +    s.a[i] = 64 - i;
> +  if (s[] != 64 || s[3] != 61 || s[4, 5] != 20)
> +    abort ();
> +  s[]++;
> +  s[42]++;
> +  ++s[3, 2];
> +  if (s.a[0] != 65 || s.a[42] != 23 || s.a[19] != 46)
> +    abort ();
> +  T t;
> +  U u;
> +  if (&u[t] != &buf[42])
> +    abort ();
> +  if (&t[u] != &buf[42])
> +    abort ();
> +}
> --- gcc/testsuite/g++.dg/cpp23/subscript2.C.jj	2021-10-13 16:12:32.811422262 +0200
> +++ gcc/testsuite/g++.dg/cpp23/subscript2.C	2021-10-13 16:30:59.199509921 +0200
> @@ -0,0 +1,51 @@
> +// P2128R6
> +// { dg-do compile }
> +// { dg-options "-std=c++23" }
> +
> +struct S
> +{
> +  S () : a {} {};
> +  int &operator[] () { return a[0]; }
> +  int &operator[] (int x) { return a[x]; }
> +  int &operator[] (int x, long y) { return a[x + y * 8]; }
> +  int a[64];
> +};
> +
> +struct T
> +{
> +  operator int () { return 42; };
> +};
> +
> +int buf[64];
> +
> +struct U
> +{
> +  operator int * () { return buf; }
> +};
> +
> +struct V
> +{
> +  V () : a {} {};
> +  V (int x, int y, int z) : a {x, y, z} {};
> +  int &operator[] () { return a[0]; }				// { dg-message "candidate" }
> +  int &operator[] (int x, long y) { return a[x + y * 8]; }	// { dg-message "candidate" }
> +  int a[64];
> +};
> +
> +void
> +foo ()
> +{
> +  S s;
> +  T t;
> +  U u;
> +  V v;
> +  auto &a = buf[];		// { dg-error "built-in subscript operator without expression list" }
> +  auto &b = buf[1, 2];		// { dg-warning "top-level comma expression in array subscript changed meaning in" }
> +  auto &c = s[1, 2, 3];		// { dg-warning "top-level comma expression in array subscript changed meaning in" }
> +  auto &d = v[1];		// { dg-error "no match for 'operator\\\[\\\]' in 'v\\\[1\\\]' \\\(operand types are 'V' and 'int'\\\)" }
> +  auto &e = v[1, 2, 3];		// { dg-error "no match for call to 'V::operator\\\[\\\] \\\(int, int, int\\\)'" }
> +  auto &f = t[42, u];		// { dg-warning "top-level comma expression in array subscript changed meaning in" }
> +  auto &g = u[42, t];		// { dg-warning "top-level comma expression in array subscript changed meaning in" }
> +  auto &h = buf[42, 2.5];	// { dg-warning "top-level comma expression in array subscript changed meaning in" }
> +				// { dg-error "invalid types \[^\n\r]* for array subscript" "" { target *-*-* } .-1 }
> +}
> --- gcc/testsuite/g++.dg/cpp23/subscript3.C.jj	2021-10-13 19:37:10.328203173 +0200
> +++ gcc/testsuite/g++.dg/cpp23/subscript3.C	2021-10-13 19:40:07.297662178 +0200
> @@ -0,0 +1,90 @@
> +// P2128R6
> +// { dg-do run }
> +// { dg-options "-std=c++23" }
> +
> +extern "C" void abort ();
> +
> +struct S
> +{
> +  constexpr S () : a {} {};
> +  constexpr S (int x, int y, int z) : a {x, y, z} {};
> +  constexpr int &operator[] () { return a[0]; }
> +  constexpr int &operator[] (int x) { return a[x]; }
> +  constexpr int &operator[] (int x, long y) { return a[x + y * 8]; }
> +  int a[64];
> +};
> +
> +struct T
> +{
> +  operator int () { return 42; };
> +};
> +
> +int buf[64];
> +
> +struct U
> +{
> +  operator int * () { return buf; }
> +};
> +
> +template <int N>
> +void
> +foo ()
> +{
> +  static_assert (S ()[1] == 0);
> +  static_assert (S (1, 2, 42)[2] == 42);
> +  static_assert (S ()[3, 4] == 0);
> +  static_assert (S (1, 43, 2)[1, 0] == 43);
> +  static_assert (S ()[] == 0);
> +  static_assert (S (44, 1, 2)[] == 44);
> +  S s;
> +  for (int i = 0; i < 64; i++)
> +    s.a[i] = 64 - i;
> +  if (s[] != 64 || s[3] != 61 || s[4, 5] != 20)
> +    abort ();
> +  s[]++;
> +  s[42]++;
> +  ++s[3, 2];
> +  if (s.a[0] != 65 || s.a[42] != 23 || s.a[19] != 46)
> +    abort ();
> +  T t;
> +  U u;
> +  if (&u[t] != &buf[42])
> +    abort ();
> +  if (&t[u] != &buf[42])
> +    abort ();
> +}
> +
> +template <typename V, typename W, typename X>
> +void
> +bar ()
> +{
> +  static_assert (V ()[1] == 0);
> +  static_assert (V (1, 2, 42)[2] == 42);
> +  static_assert (V ()[3, 4] == 0);
> +  static_assert (V (1, 43, 2)[1, 0] == 43);
> +  static_assert (V ()[] == 0);
> +  static_assert (V (44, 1, 2)[] == 44);
> +  V s;
> +  for (int i = 0; i < 64; i++)
> +    s.a[i] = 64 - i;
> +  if (s[] != 64 || s[3] != 61 || s[4, 5] != 20)
> +    abort ();
> +  s[]++;
> +  s[42]++;
> +  ++s[3, 2];
> +  if (s.a[0] != 65 || s.a[42] != 23 || s.a[19] != 46)
> +    abort ();
> +  W t;
> +  X u;
> +  if (&u[t] != &buf[42])
> +    abort ();
> +  if (&t[u] != &buf[42])
> +    abort ();
> +}
> +
> +int
> +main ()
> +{
> +  foo <0> ();
> +  bar <S, T, U> ();
> +}
> --- gcc/testsuite/g++.dg/cpp23/subscript4.C.jj	2021-10-13 19:41:26.899519224 +0200
> +++ gcc/testsuite/g++.dg/cpp23/subscript4.C	2021-10-13 19:54:11.402539793 +0200
> @@ -0,0 +1,44 @@
> +// P2128R6
> +// { dg-do run }
> +// { dg-options "-std=c++23" }
> +
> +extern "C" void abort ();
> +
> +struct S
> +{
> +  constexpr S () : a {} {};
> +  constexpr S (int x, int y, int z) : a {x, y, z} {};
> +  constexpr int &operator[] () { return a[0]; }
> +  constexpr int &operator[] (int x) { return a[x]; }
> +  constexpr int &operator[] (int x, long y) { return a[x + y * 8]; }
> +  int a[64];
> +};
> +int buf[26];
> +
> +template <class ...Ts>
> +auto &
> +foo (S &s, Ts... args)
> +{
> +  return s[args...];
> +}
> +
> +template <typename T, class ...Ts>
> +auto &
> +bar (T &s, Ts... args)
> +{
> +  return s[args...];
> +}
> +
> +int
> +main ()
> +{
> +  S s;
> +  if (&foo (s) != &s.a[0]
> +      || &foo (s, 42) != &s.a[42]
> +      || &foo (s, 5, 4) != &s.a[37]
> +      || &bar (s) != &s.a[0]
> +      || &bar (s, 22) != &s.a[22]
> +      || &bar (s, 17, 3L) != &s.a[41]
> +      || &bar (buf, 5) != &buf[5])
> +    abort ();
> +}
> --- gcc/testsuite/g++.dg/cpp23/subscript5.C.jj	2021-10-14 10:09:30.316991001 +0200
> +++ gcc/testsuite/g++.dg/cpp23/subscript5.C	2021-10-14 10:08:54.357511795 +0200
> @@ -0,0 +1,28 @@
> +// P2128R6
> +// { dg-do run { target c++11 } }
> +
> +#include <initializer_list>
> +#include <cstdlib>
> +
> +struct S
> +{
> +  S () : a {} {};
> +  int &operator[] (std::initializer_list<int> l) {
> +    int sum = 0;
> +    for (auto x : l)
> +      sum += x;
> +    return a[sum];
> +  }
> +  int a[64];
> +};
> +
> +int
> +main ()
> +{
> +  S s;
> +  if (&s[{0}] != &s.a[0]
> +      || &s[{42}] != &s.a[42]
> +      || &s[{5, 7, 9}] != &s.a[5 + 7 + 9]
> +      || &s[{1, 2, 3, 4}] != &s.a[1 + 2 + 3 + 4])
> +    abort ();
> +}
> --- gcc/testsuite/g++.dg/cpp23/subscript6.C.jj	2021-10-14 10:11:34.964185752 +0200
> +++ gcc/testsuite/g++.dg/cpp23/subscript6.C	2021-10-14 10:11:29.545264236 +0200
> @@ -0,0 +1,31 @@
> +// P2128R6
> +// { dg-do run }
> +// { dg-options "-std=c++23" }
> +
> +#include <initializer_list>
> +#include <cstdlib>
> +
> +struct S
> +{
> +  S () : a {} {};
> +  int &operator[] (std::initializer_list<int> l, std::initializer_list<int> m) {
> +    int sum = 0;
> +    for (auto x : l)
> +      sum += x;
> +    for (auto x : m)
> +      sum += x;
> +    return a[sum];
> +  }
> +  int a[64];
> +};
> +
> +int
> +main ()
> +{
> +  S s;
> +  if (&s[{0}, {3, 1, 2}] != &s.a[0 + 3 + 1 + 2]
> +      || &s[{42}, {11, 1}] != &s.a[42 + 11 + 1]
> +      || &s[{5, 7, 9}, {3}] != &s.a[5 + 7 + 9 + 3]
> +      || &s[{1, 2, 3, 4}, {3, 5, 8}] != &s.a[1 + 2 + 3 + 4 + 3 + 5 + 8])
> +    abort ();
> +}
> 
> 	Jakub
>
  

Patch

--- gcc/doc/invoke.texi.jj	2021-10-12 09:08:25.781088065 +0200
+++ gcc/doc/invoke.texi	2021-10-13 15:26:49.284891872 +0200
@@ -3461,19 +3461,27 @@  about ABI tags.
 @opindex Wcomma-subscript
 @opindex Wno-comma-subscript
 Warn about uses of a comma expression within a subscripting expression.
-This usage was deprecated in C++20.  However, a comma expression wrapped
-in @code{( )} is not deprecated.  Example:
+This usage was deprecated in C++20 and is going to be removed in C++23.
+However, a comma expression wrapped in @code{( )} is not deprecated.  Example:
 
 @smallexample
 @group
 void f(int *a, int b, int c) @{
-    a[b,c];     // deprecated
+    a[b,c];     // deprecated in C++20, invalid in C++23
     a[(b,c)];   // OK
 @}
 @end group
 @end smallexample
 
-Enabled by default with @option{-std=c++20}.
+In C++23 it is valid to have comma separated expressions in a subscript
+when an overloaded subscript operator is found and supports the right
+number and types of arguments.  G++ will accept the formerly valid syntax
+for code that is not valid in C++23 but used to be valid but deprecated
+in C++20 with a pedantic warning that can be disabled with
+@option{-Wno-comma-subscript}.
+
+Enabled by default with @option{-std=c++20} unless @option{-Wno-deprecated},
+and with @option{-std=c++23} regardless of @option{-Wno-deprecated}.
 
 @item -Wctad-maybe-unsupported @r{(C++ and Objective-C++ only)}
 @opindex Wctad-maybe-unsupported
--- gcc/c-family/c-opts.c.jj	2021-10-09 10:07:51.697707634 +0200
+++ gcc/c-family/c-opts.c	2021-10-13 15:18:50.251789524 +0200
@@ -946,7 +946,8 @@  c_common_post_options (const char **pfil
   /* -Wcomma-subscript is enabled by default in C++20.  */
   SET_OPTION_IF_UNSET (&global_options, &global_options_set,
 		       warn_comma_subscript,
-		       cxx_dialect >= cxx20 && warn_deprecated);
+		       cxx_dialect >= cxx23
+		       || (cxx_dialect == cxx20 && warn_deprecated));
 
   /* -Wvolatile is enabled by default in C++20.  */
   SET_OPTION_IF_UNSET (&global_options, &global_options_set, warn_volatile,
--- gcc/c-family/c-cppbuiltin.c.jj	2021-10-06 10:28:16.228726784 +0200
+++ gcc/c-family/c-cppbuiltin.c	2021-10-13 16:37:53.030557910 +0200
@@ -1073,6 +1073,7 @@  c_cpp_builtins (cpp_reader *pfile)
 	  cpp_define (pfile, "__cpp_size_t_suffix=202011L");
 	  cpp_define (pfile, "__cpp_if_consteval=202106L");
 	  cpp_define (pfile, "__cpp_constexpr=202110L");
+	  cpp_define (pfile, "__cpp_multidimensional_subscript=202110L");
 	}
       if (flag_concepts)
         {
--- gcc/cp/cp-tree.h.jj	2021-10-06 10:11:38.296564499 +0200
+++ gcc/cp/cp-tree.h	2021-10-13 20:17:57.050035862 +0200
@@ -6465,6 +6465,9 @@  inline tree build_new_op (const op_locat
 }
 extern tree build_op_call			(tree, vec<tree, va_gc> **,
 						 tsubst_flags_t);
+extern tree build_op_subscript			(const op_location_t &, tree,
+						 vec<tree, va_gc> **, tree *,
+						 tsubst_flags_t);
 extern bool aligned_allocation_fn_p		(tree);
 extern tree destroying_delete_p			(tree);
 extern bool usual_deallocation_fn_p		(tree);
@@ -6806,7 +6809,8 @@  extern void maybe_make_one_only			(tree)
 extern bool vague_linkage_p			(tree);
 extern void grokclassfn				(tree, tree,
 						 enum overload_flags);
-extern tree grok_array_decl			(location_t, tree, tree, bool);
+extern tree grok_array_decl			(location_t, tree, tree,
+						 vec<tree, va_gc> **, tsubst_flags_t);
 extern tree delete_sanity			(location_t, tree, tree, bool,
 						 int, tsubst_flags_t);
 extern tree check_classfn			(tree, tree, tree);
@@ -7703,6 +7707,8 @@  extern tree build_min_nt_loc			(location
 						 ...);
 extern tree build_min_non_dep			(enum tree_code, tree, ...);
 extern tree build_min_non_dep_op_overload	(enum tree_code, tree, tree, ...);
+extern tree build_min_non_dep_op_overload	(tree, tree, tree,
+						 vec<tree, va_gc> *);
 extern tree build_min_nt_call_vec (tree, vec<tree, va_gc> *);
 extern tree build_min_non_dep_call_vec		(tree, tree, vec<tree, va_gc> *);
 extern vec<tree, va_gc>* vec_copy_and_insert    (vec<tree, va_gc>*, tree, unsigned);
--- gcc/cp/parser.c.jj	2021-10-12 09:08:08.689334252 +0200
+++ gcc/cp/parser.c	2021-10-13 19:28:02.970059048 +0200
@@ -7885,6 +7885,7 @@  cp_parser_postfix_expression (cp_parser
 
      postfix-expression [ expression ]
      postfix-expression [ braced-init-list ] (C++11)
+     postfix-expression [ expression-list[opt] ] (C++23)
 
    FOR_OFFSETOF is set if we're being called in that context, which
    changes how we deal with integer constant expressions.  */
@@ -7896,6 +7897,7 @@  cp_parser_postfix_open_square_expression
 					  bool decltype_p)
 {
   tree index = NULL_TREE;
+  releasing_vec expression_list = NULL;
   location_t loc = cp_lexer_peek_token (parser->lexer)->location;
   bool saved_greater_than_is_operator_p;
 
@@ -7917,7 +7919,63 @@  cp_parser_postfix_open_square_expression
     index = cp_parser_constant_expression (parser);
   else
     {
-      if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE))
+      if (cxx_dialect >= cxx23
+	  && cp_lexer_next_token_is (parser->lexer, CPP_CLOSE_SQUARE))
+	*&expression_list = make_tree_vector ();
+      else if (cxx_dialect >= cxx23)
+	{
+	  while (true)
+	    {
+	      cp_expr expr (NULL_TREE);
+	      /* Parse the next assignment-expression.  */
+	      if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE))
+		{
+		  /* A braced-init-list.  */
+		  bool expr_nonconst_p;
+		  cp_lexer_set_source_position (parser->lexer);
+		  expr = cp_parser_braced_list (parser, &expr_nonconst_p);
+		}
+	      else
+		expr = cp_parser_assignment_expression (parser);
+
+	      /* If we have an ellipsis, then this is an expression
+		 expansion.  */
+	      if (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS))
+		{
+		  /* Consume the `...'.  */
+		  cp_lexer_consume_token (parser->lexer);
+		  /* Build the argument pack.  */
+		  expr = make_pack_expansion (expr);
+		}
+
+	      if (expr == error_mark_node)
+		index = error_mark_node;
+	      else if (expression_list.get () == NULL
+		       && !PACK_EXPANSION_P (expr.get_value ()))
+		index = expr.get_value ();
+	      else
+		vec_safe_push (expression_list, expr.get_value ());
+
+	      /* If the next token isn't a `,', then we are done.  */
+	      if (cp_lexer_next_token_is_not (parser->lexer, CPP_COMMA))
+		break;
+
+	      if (expression_list.get () == NULL && index != error_mark_node)
+		{
+		  *&expression_list = make_tree_vector_single (index);
+		  index = NULL_TREE;
+		}
+
+	      /* Otherwise, consume the `,' and keep going.  */
+	      cp_lexer_consume_token (parser->lexer);
+	    }
+	  if (expression_list.get () && index == error_mark_node)
+	    {
+	      release_tree_vector (*&expression_list);
+	      *&expression_list = NULL;
+	    }
+	}
+      else if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE))
 	{
 	  bool expr_nonconst_p;
 	  cp_lexer_set_source_position (parser->lexer);
@@ -7937,7 +7995,9 @@  cp_parser_postfix_open_square_expression
 
   /* Build the ARRAY_REF.  */
   postfix_expression = grok_array_decl (loc, postfix_expression,
-					index, decltype_p);
+					index, &expression_list,
+					tf_warning_or_error
+					| (decltype_p ? tf_decltype : 0));
 
   /* When not doing offsetof, array references are not permitted in
      constant-expressions.  */
@@ -10607,8 +10667,8 @@  cp_parser_builtin_offsetof (cp_parser *p
 
 	case CPP_DEREF:
 	  /* offsetof-member-designator "->" identifier */
-	  expr = grok_array_decl (token->location, expr,
-				  integer_zero_node, false);
+	  expr = grok_array_decl (token->location, expr, integer_zero_node,
+				  NULL, tf_warning_or_error);
 	  /* FALLTHRU */
 
 	case CPP_DOT:
--- gcc/cp/decl.c.jj	2021-10-12 09:08:08.640334958 +0200
+++ gcc/cp/decl.c	2021-10-13 11:36:19.587853088 +0200
@@ -15151,6 +15151,8 @@  grok_op_properties (tree decl, bool comp
     case OVL_OP_FLAG_BINARY:
       if (arity != 2)
 	{
+	  if (operator_code == ARRAY_REF && cxx_dialect >= cxx23)
+	    break;
 	  error_at (loc,
 		    methodp
 		    ? G_("%qD must have exactly one argument")
--- gcc/cp/decl2.c.jj	2021-09-09 10:40:26.077175937 +0200
+++ gcc/cp/decl2.c	2021-10-13 20:20:26.892879333 +0200
@@ -363,16 +363,20 @@  grokclassfn (tree ctype, tree function,
 }
 
 /* Create an ARRAY_REF, checking for the user doing things backwards
-   along the way.  DECLTYPE_P is for N3276, as in the parser.  */
+   along the way.
+   If INDEX_EXP is non-NULL, then that is the index expression,
+   otherwise INDEX_EXP_LIST is the list of index expressions.  */
 
 tree
 grok_array_decl (location_t loc, tree array_expr, tree index_exp,
-		 bool decltype_p)
+		 vec<tree, va_gc> **index_exp_list, tsubst_flags_t complain)
 {
   tree type;
   tree expr;
   tree orig_array_expr = array_expr;
   tree orig_index_exp = index_exp;
+  vec<tree, va_gc> *orig_index_exp_list
+    = index_exp_list ? *index_exp_list : NULL;
   tree overload = NULL_TREE;
 
   if (error_operand_p (array_expr) || error_operand_p (index_exp))
@@ -381,11 +385,23 @@  grok_array_decl (location_t loc, tree ar
   if (processing_template_decl)
     {
       if (type_dependent_expression_p (array_expr)
-	  || type_dependent_expression_p (index_exp))
-	return build_min_nt_loc (loc, ARRAY_REF, array_expr, index_exp,
-				 NULL_TREE, NULL_TREE);
+	  || (index_exp ? type_dependent_expression_p (index_exp)
+			: any_type_dependent_arguments_p (*index_exp_list)))
+	{
+	  if (index_exp == NULL)
+	    index_exp = build_min_nt_call_vec (ovl_op_identifier (ARRAY_REF),
+					       *index_exp_list);
+	  return build_min_nt_loc (loc, ARRAY_REF, array_expr, index_exp,
+				   NULL_TREE, NULL_TREE);
+	}
       array_expr = build_non_dependent_expr (array_expr);
-      index_exp = build_non_dependent_expr (index_exp);
+      if (index_exp)
+	index_exp = build_non_dependent_expr (index_exp);
+      else
+	{
+	  orig_index_exp_list = make_tree_vector_copy (*index_exp_list);
+	  make_args_non_dependent (*index_exp_list);
+	}
     }
 
   type = TREE_TYPE (array_expr);
@@ -393,13 +409,54 @@  grok_array_decl (location_t loc, tree ar
   type = non_reference (type);
 
   /* If they have an `operator[]', use that.  */
-  if (MAYBE_CLASS_TYPE_P (type) || MAYBE_CLASS_TYPE_P (TREE_TYPE (index_exp)))
+  if (MAYBE_CLASS_TYPE_P (type)
+      || (index_exp && MAYBE_CLASS_TYPE_P (TREE_TYPE (index_exp)))
+      || (index_exp == NULL_TREE
+	  && !(*index_exp_list)->is_empty ()
+	  && MAYBE_CLASS_TYPE_P (TREE_TYPE ((*index_exp_list)->last ()))))
     {
-      tsubst_flags_t complain = tf_warning_or_error;
-      if (decltype_p)
-	complain |= tf_decltype;
-      expr = build_new_op (loc, ARRAY_REF, LOOKUP_NORMAL, array_expr,
-			   index_exp, NULL_TREE, &overload, complain);
+      if (index_exp)
+	expr = build_new_op (loc, ARRAY_REF, LOOKUP_NORMAL, array_expr,
+			     index_exp, NULL_TREE, &overload, complain);
+      else if ((*index_exp_list)->is_empty ())
+	expr = build_op_subscript (loc, array_expr, index_exp_list, &overload,
+				   complain);
+      else
+	{
+	  expr = build_op_subscript (loc, array_expr, index_exp_list,
+				     &overload, complain & tf_decltype);
+	  if (expr == error_mark_node)
+	    {
+	      tree idx = NULL_TREE;
+	      unsigned int i;
+	      tree e;
+	      FOR_EACH_VEC_SAFE_ELT (*index_exp_list, i, e)
+		if (idx == NULL_TREE)
+		  idx = e;
+		else
+		  {
+		    idx = build_x_compound_expr (loc, idx, e, tf_none);
+		    if (idx == error_mark_node)
+		      break;
+		  }
+	      if (idx != error_mark_node)
+		expr = build_new_op (loc, ARRAY_REF, LOOKUP_NORMAL, array_expr,
+				     idx, NULL_TREE, &overload,
+				     complain & tf_decltype);
+	      if (expr == error_mark_node)
+		{
+		  overload = NULL_TREE;
+		  expr = build_op_subscript (loc, array_expr, index_exp_list,
+					     &overload, complain);
+		}
+	      else
+		/* If it would be valid albeit deprecated expression in C++20,
+		   just pedwarn on it and treat it as if wrapped in ().  */
+		pedwarn (loc, OPT_Wcomma_subscript,
+			 "top-level comma expression in array subscript "
+			 "changed meaning in C++23");
+	    }
+	}
     }
   else
     {
@@ -415,6 +472,41 @@  grok_array_decl (location_t loc, tree ar
       else
 	p1 = build_expr_type_conversion (WANT_POINTER, array_expr, false);
 
+      if (index_exp == NULL_TREE)
+	{
+	  if ((*index_exp_list)->is_empty ())
+	    {
+	      error_at (loc, "built-in subscript operator without expression "
+			     "list");
+	      return error_mark_node;
+	    }
+	  tree idx = NULL_TREE;
+	  unsigned int i;
+	  tree e;
+	  FOR_EACH_VEC_SAFE_ELT (*index_exp_list, i, e)
+	    if (idx == NULL_TREE)
+	      idx = e;
+	    else
+	      {
+		idx = build_x_compound_expr (loc, idx, e, tf_none);
+		if (idx == error_mark_node)
+		  break;
+	      }
+	  if (idx != error_mark_node)
+	    /* If it would be valid albeit deprecated expression in C++20,
+	       just pedwarn on it and treat it as if wrapped in ().  */
+	    pedwarn (loc, OPT_Wcomma_subscript,
+		     "top-level comma expression in array subscript "
+		     "changed meaning in C++23");
+	  else
+	    {
+	      error_at (loc, "built-in subscript operator with more than one "
+			     "expression in expression list");
+	      return error_mark_node;
+	    }
+	  index_exp = idx;
+	}
+
       if (TREE_CODE (TREE_TYPE (index_exp)) == ARRAY_TYPE)
 	p2 = index_exp;
       else
@@ -457,11 +549,30 @@  grok_array_decl (location_t loc, tree ar
   if (processing_template_decl && expr != error_mark_node)
     {
       if (overload != NULL_TREE)
-	return (build_min_non_dep_op_overload
-		(ARRAY_REF, expr, overload, orig_array_expr, orig_index_exp));
+	{
+	  if (orig_index_exp == NULL_TREE)
+	    {
+	      expr = build_min_non_dep_op_overload (expr, overload,
+						    orig_array_expr,
+						    orig_index_exp_list);
+	      release_tree_vector (orig_index_exp_list);
+	      return expr;
+	    }
+	  return build_min_non_dep_op_overload (ARRAY_REF, expr, overload,
+						orig_array_expr,
+						orig_index_exp);
+	}
+
+      if (orig_index_exp == NULL_TREE)
+	{
+	  orig_index_exp
+	    = build_min_nt_call_vec (ovl_op_identifier (ARRAY_REF),
+				     orig_index_exp_list);
+	  release_tree_vector (orig_index_exp_list);
+	}
 
-      return build_min_non_dep (ARRAY_REF, expr, orig_array_expr, orig_index_exp,
-				NULL_TREE, NULL_TREE);
+      return build_min_non_dep (ARRAY_REF, expr, orig_array_expr,
+				orig_index_exp, NULL_TREE, NULL_TREE);
     }
   return expr;
 }
--- gcc/cp/tree.c.jj	2021-10-05 22:28:31.072246826 +0200
+++ gcc/cp/tree.c	2021-10-13 20:22:08.407418346 +0200
@@ -3671,13 +3671,42 @@  build_min_non_dep_op_overload (enum tree
 	}
     }
   else
-   gcc_unreachable ();
+    gcc_unreachable ();
 
   va_end (p);
   call = build_min_non_dep_call_vec (non_dep, fn, args);
 
   tree call_expr = extract_call_expr (call);
   KOENIG_LOOKUP_P (call_expr) = KOENIG_LOOKUP_P (non_dep);
+  CALL_EXPR_OPERATOR_SYNTAX (call_expr) = true;
+  CALL_EXPR_ORDERED_ARGS (call_expr) = CALL_EXPR_ORDERED_ARGS (non_dep);
+  CALL_EXPR_REVERSE_ARGS (call_expr) = CALL_EXPR_REVERSE_ARGS (non_dep);
+
+  return call;
+}
+
+/* Similar to above build_min_non_dep_op_overload, but arguments
+   are taken from ARGS vector.  */
+
+tree
+build_min_non_dep_op_overload (tree non_dep, tree overload, tree object,
+			       vec<tree, va_gc> *args)
+{
+  non_dep = extract_call_expr (non_dep);
+
+  unsigned int nargs = call_expr_nargs (non_dep);
+  gcc_assert (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE);
+  tree binfo = TYPE_BINFO (TREE_TYPE (object));
+  tree method = build_baselink (binfo, binfo, overload, NULL_TREE);
+  tree fn = build_min (COMPONENT_REF, TREE_TYPE (overload),
+		       object, method, NULL_TREE);
+  nargs--;
+  gcc_assert (vec_safe_length (args) == nargs);
+
+  tree call = build_min_non_dep_call_vec (non_dep, fn, args);
+
+  tree call_expr = extract_call_expr (call);
+  KOENIG_LOOKUP_P (call_expr) = KOENIG_LOOKUP_P (non_dep);
   CALL_EXPR_OPERATOR_SYNTAX (call_expr) = true;
   CALL_EXPR_ORDERED_ARGS (call_expr) = CALL_EXPR_ORDERED_ARGS (non_dep);
   CALL_EXPR_REVERSE_ARGS (call_expr) = CALL_EXPR_REVERSE_ARGS (non_dep);
--- gcc/cp/call.c.jj	2021-09-16 10:51:02.246976887 +0200
+++ gcc/cp/call.c	2021-10-13 14:20:05.939497079 +0200
@@ -6973,6 +6973,129 @@  build_new_op (const op_location_t &loc,
   return ret;
 }
 
+/* Build a new call to operator[].  This may change ARGS.  */
+
+static tree
+build_op_subscript_1 (const op_location_t &loc, tree obj,
+		      vec<tree, va_gc> **args, tree *overload,
+		      tsubst_flags_t complain)
+{
+  struct z_candidate *candidates = 0, *cand;
+  tree fns, first_mem_arg = NULL_TREE;
+  bool any_viable_p;
+  tree result = NULL_TREE;
+  void *p;
+
+  obj = mark_lvalue_use (obj);
+
+  if (error_operand_p (obj))
+    return error_mark_node;
+
+  tree type = TREE_TYPE (obj);
+
+  obj = prep_operand (obj);
+
+  if (TYPE_BINFO (type))
+    {
+      fns = lookup_fnfields (TYPE_BINFO (type), ovl_op_identifier (ARRAY_REF),
+			     1, complain);
+      if (fns == error_mark_node)
+	return error_mark_node;
+    }
+  else
+    fns = NULL_TREE;
+
+  if (args != NULL && *args != NULL)
+    {
+      *args = resolve_args (*args, complain);
+      if (*args == NULL)
+	return error_mark_node;
+    }
+
+  /* Get the high-water mark for the CONVERSION_OBSTACK.  */
+  p = conversion_obstack_alloc (0);
+
+  if (fns)
+    {
+      first_mem_arg = obj;
+
+      add_candidates (BASELINK_FUNCTIONS (fns),
+		      first_mem_arg, *args, NULL_TREE,
+		      NULL_TREE, false,
+		      BASELINK_BINFO (fns), BASELINK_ACCESS_BINFO (fns),
+		      LOOKUP_NORMAL, &candidates, complain);
+    }
+
+  /* Be strict here because if we choose a bad conversion candidate, the
+     errors we get won't mention the call context.  */
+  candidates = splice_viable (candidates, true, &any_viable_p);
+  if (!any_viable_p)
+    {
+      if (complain & tf_error)
+	{
+	  auto_diagnostic_group d;
+	  error ("no match for call to %<%T::operator[] (%A)%>",
+		 TREE_TYPE (obj), build_tree_list_vec (*args));
+	  print_z_candidates (loc, candidates);
+	}
+      result = error_mark_node;
+    }
+  else
+    {
+      cand = tourney (candidates, complain);
+      if (cand == 0)
+	{
+	  if (complain & tf_error)
+	    {
+	      auto_diagnostic_group d;
+	      error ("call of %<%T::operator[] (%A)%> is ambiguous",
+		     TREE_TYPE (obj), build_tree_list_vec (*args));
+	      print_z_candidates (loc, candidates);
+	    }
+	  result = error_mark_node;
+	}
+      else if (TREE_CODE (cand->fn) == FUNCTION_DECL
+	       && DECL_OVERLOADED_OPERATOR_P (cand->fn)
+	       && DECL_OVERLOADED_OPERATOR_IS (cand->fn, ARRAY_REF))
+	{
+	  if (overload)
+	    *overload = cand->fn;
+	  result = build_over_call (cand, LOOKUP_NORMAL, complain);
+	  if (trivial_fn_p (cand->fn) || DECL_IMMEDIATE_FUNCTION_P (cand->fn))
+	    /* There won't be a CALL_EXPR.  */;
+	  else if (result && result != error_mark_node)
+	    {
+	      tree call = extract_call_expr (result);
+	      CALL_EXPR_OPERATOR_SYNTAX (call) = true;
+
+	      /* Specify evaluation order as per P0145R2.  */
+	      CALL_EXPR_ORDERED_ARGS (call) = op_is_ordered (ARRAY_REF) == 1;
+	    }
+	}
+      else
+	gcc_unreachable ();
+    }
+
+  /* Free all the conversions we allocated.  */
+  obstack_free (&conversion_obstack, p);
+
+  return result;
+}
+
+/* Wrapper for above.  */
+
+tree
+build_op_subscript (const op_location_t &loc, tree obj,
+		    vec<tree, va_gc> **args, tree *overload,
+		    tsubst_flags_t complain)
+{
+  tree ret;
+  bool subtime = timevar_cond_start (TV_OVERLOAD);
+  ret = build_op_subscript_1 (loc, obj, args, overload, complain);
+  timevar_cond_stop (TV_OVERLOAD, subtime);
+  return ret;
+}
+
 /* CALL was returned by some call-building function; extract the actual
    CALL_EXPR from any bits that have been tacked on, e.g. by
    convert_from_reference.  */
--- gcc/cp/pt.c.jj	2021-10-08 10:52:47.060532186 +0200
+++ gcc/cp/pt.c	2021-10-13 19:29:47.705556070 +0200
@@ -20038,6 +20038,56 @@  tsubst_copy_and_build (tree t,
     case ARRAY_REF:
       op1 = tsubst_non_call_postfix_expression (TREE_OPERAND (t, 0),
 						args, complain, in_decl);
+      if (TREE_CODE (TREE_OPERAND (t, 1)) == CALL_EXPR
+	  && (CALL_EXPR_FN (TREE_OPERAND (t, 1))
+	      == ovl_op_identifier (ARRAY_REF)))
+	{
+	  tree c = TREE_OPERAND (t, 1);
+	  unsigned nargs = call_expr_nargs (c), i;
+	  releasing_vec index_exp_list;
+	  for (i = 0; i < nargs; ++i)
+	    {
+	      tree arg = CALL_EXPR_ARG (c, i);
+
+	      if (!PACK_EXPANSION_P (arg))
+		vec_safe_push (index_exp_list, RECUR (arg));
+	      else
+		{
+		  /* Expand the pack expansion and push each entry onto
+		     INDEX_EXP_LIST.  */
+		  arg = tsubst_pack_expansion (arg, args, complain, in_decl);
+		  if (TREE_CODE (arg) == TREE_VEC)
+		    {
+		      unsigned int len, j;
+
+		      len = TREE_VEC_LENGTH (arg);
+		      for (j = 0; j < len; ++j)
+			{
+			  tree value = TREE_VEC_ELT (arg, j);
+			  if (value != NULL_TREE)
+			    value = convert_from_reference (value);
+			  vec_safe_push (index_exp_list, value);
+			}
+		    }
+		  else
+		    {
+		      /* A partial substitution.  Add one entry.  */
+		      vec_safe_push (index_exp_list, arg);
+		    }
+		}
+	    }
+	  tree r;
+	  if (vec_safe_length (index_exp_list) == 1
+	      && !PACK_EXPANSION_P (index_exp_list[0]))
+	    r = grok_array_decl (EXPR_LOCATION (t), op1,
+				 index_exp_list[0], NULL,
+				 complain | decltype_flag);
+	  else
+	    r = grok_array_decl (EXPR_LOCATION (t), op1,
+				 NULL_TREE, &index_exp_list,
+				 complain | decltype_flag);
+	  RETURN (r);
+	}
       RETURN (build_x_array_ref (EXPR_LOCATION (t), op1,
 				 RECUR (TREE_OPERAND (t, 1)),
 				 complain|decltype_flag));
--- gcc/cp/semantics.c.jj	2021-09-22 09:29:01.061813863 +0200
+++ gcc/cp/semantics.c	2021-10-13 17:51:13.294341545 +0200
@@ -5362,7 +5362,8 @@  handle_omp_array_sections_1 (tree c, tre
 		      OMP_CLAUSE_CODE (c) == OMP_CLAUSE_REDUCTION
 		      || OMP_CLAUSE_CODE (c) == OMP_CLAUSE_IN_REDUCTION
 		      || OMP_CLAUSE_CODE (c) == OMP_CLAUSE_TASK_REDUCTION);
-  ret = grok_array_decl (OMP_CLAUSE_LOCATION (c), ret, low_bound, false);
+  ret = grok_array_decl (OMP_CLAUSE_LOCATION (c), ret, low_bound, NULL,
+			 tf_warning_or_error);
   return ret;
 }
 
--- gcc/testsuite/g++.dg/cpp2a/comma1.C.jj	2020-07-28 15:39:09.998756365 +0200
+++ gcc/testsuite/g++.dg/cpp2a/comma1.C	2021-10-13 15:45:37.670653141 +0200
@@ -8,19 +8,24 @@  struct S {
 void
 fn (int *a, int b, int c)
 {
-  a[b,c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20 } }
+  a[b,c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_only } }
+	  // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[(b,c)];
 
-  a[(void) b, c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20 } }
+  a[(void) b, c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_only } }
+		  // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[((void) b, c)];
 
-  a[(void) b, (void) c, (void) b, b]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20 } }
+  a[(void) b, (void) c, (void) b, b]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_only } }
+				      // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[((void) b, (void) c, (void) b, b)];
 
-  a[S(), 10]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20 } }
+  a[S(), 10]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_only } }
+	      // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[(S(), 10)];
 
   a[int{(1,2)}];
-  a[int{(1,2)}, int{}]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20 } }
+  a[int{(1,2)}, int{}]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_only } }
+			// { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[(int{(1,2)}, int{})];
 }
--- gcc/testsuite/g++.dg/cpp2a/comma3.C.jj	2020-01-12 11:54:37.135402516 +0100
+++ gcc/testsuite/g++.dg/cpp2a/comma3.C	2021-10-13 15:30:35.134639835 +0200
@@ -9,19 +9,24 @@  struct S {
 void
 fn (int *a, int b, int c)
 {
-  a[b,c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
+  a[b,c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
+	  // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[(b,c)];
 
-  a[(void) b, c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
+  a[(void) b, c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
+		  // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[((void) b, c)];
 
-  a[(void) b, (void) c, (void) b, b]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
+  a[(void) b, (void) c, (void) b, b]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
+				      // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[((void) b, (void) c, (void) b, b)];
 
-  a[S(), 10]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
+  a[S(), 10]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
+	      // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[(S(), 10)];
 
   a[int{(1,2)}];
-  a[int{(1,2)}, int{}]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
+  a[int{(1,2)}, int{}]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
+			// { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[(int{(1,2)}, int{})];
 }
--- gcc/testsuite/g++.dg/cpp2a/comma4.C.jj	2020-07-28 15:39:09.998756365 +0200
+++ gcc/testsuite/g++.dg/cpp2a/comma4.C	2021-10-13 15:33:52.944792025 +0200
@@ -10,18 +10,23 @@  void
 fn (int *a, int b, int c)
 {
   a[b,c]; // { dg-bogus "top-level comma expression in array subscript is deprecated" }
+	  // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[(b,c)];
 
   a[(void) b, c]; // { dg-bogus "top-level comma expression in array subscript is deprecated" }
+		  // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[((void) b, c)];
 
   a[(void) b, (void) c, (void) b, b]; // { dg-bogus "top-level comma expression in array subscript is deprecated" }
+				      // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[((void) b, (void) c, (void) b, b)];
 
   a[S(), 10]; // { dg-bogus "top-level comma expression in array subscript is deprecated" }
+	      // { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[(S(), 10)];
 
   a[int{(1,2)}];
   a[int{(1,2)}, int{}]; // { dg-bogus "top-level comma expression in array subscript is deprecated" }
+			// { dg-warning "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[(int{(1,2)}, int{})];
 }
--- gcc/testsuite/g++.dg/cpp2a/comma5.C.jj	2020-07-28 15:39:09.998756365 +0200
+++ gcc/testsuite/g++.dg/cpp2a/comma5.C	2021-10-13 15:46:11.147171517 +0200
@@ -8,14 +8,20 @@  void
 fn (int *a, int b, int c)
 {
   a[foo<int, int>(1, 2)];
-  a[foo<int, int>(1, 2), foo<int, int>(3, 4)]; // { dg-warning "24:top-level comma expression in array subscript is deprecated" }
+  a[foo<int, int>(1, 2), foo<int, int>(3, 4)]; // { dg-warning "24:top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
+					       // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
 
-  a[b < c, b < c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
-  a[b < c, b > c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
-  a[b > c, b > c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
-  a[b > c, b < c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
+  a[b < c, b < c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
+		   // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
+  a[b < c, b > c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
+		   // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
+  a[b > c, b > c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
+		   // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
+  a[b > c, b < c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
+		   // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[(b < c, b < c)];
   a[(b < c, b > c)];
-  a[b << c, b << c]; // { dg-warning "top-level comma expression in array subscript is deprecated" }
+  a[b << c, b << c]; // { dg-warning "top-level comma expression in array subscript is deprecated" "" { target c++20_down } }
+		     // { dg-error "top-level comma expression in array subscript changed meaning in" "" { target c++23 } .-1 }
   a[(b << c, b << c)]; 
 }
--- gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C.jj	2021-10-06 10:28:16.256726395 +0200
+++ gcc/testsuite/g++.dg/cpp23/feat-cxx2b.C	2021-10-13 16:40:07.980616965 +0200
@@ -551,3 +551,9 @@ 
 #elif __cpp_if_consteval != 202106
 #  error "__cpp_if_consteval != 202106"
 #endif
+
+#ifndef __cpp_multidimensional_subscript
+#  error "__cpp_multidimensional_subscript"
+#elif __cpp_multidimensional_subscript != 202110
+#  error "__cpp_multidimensional_subscript != 202110"
+#endif
--- gcc/testsuite/g++.dg/cpp23/subscript1.C.jj	2021-10-13 16:04:24.030452004 +0200
+++ gcc/testsuite/g++.dg/cpp23/subscript1.C	2021-10-13 16:10:19.161344421 +0200
@@ -0,0 +1,55 @@ 
+// P2128R6
+// { dg-do run }
+// { dg-options "-std=c++23" }
+
+extern "C" void abort ();
+
+struct S
+{
+  constexpr S () : a {} {};
+  constexpr S (int x, int y, int z) : a {x, y, z} {};
+  constexpr int &operator[] () { return a[0]; }
+  constexpr int &operator[] (int x) { return a[x]; }
+  constexpr int &operator[] (int x, long y) { return a[x + y * 8]; }
+  int a[64];
+};
+
+struct T
+{
+  operator int () { return 42; };
+};
+
+int buf[64];
+
+struct U
+{
+  operator int * () { return buf; }
+};
+
+static_assert (S ()[1] == 0);
+static_assert (S (1, 2, 42)[2] == 42);
+static_assert (S ()[3, 4] == 0);
+static_assert (S (1, 43, 2)[1, 0] == 43);
+static_assert (S ()[] == 0);
+static_assert (S (44, 1, 2)[] == 44);
+
+int
+main ()
+{
+  S s;
+  for (int i = 0; i < 64; i++)
+    s.a[i] = 64 - i;
+  if (s[] != 64 || s[3] != 61 || s[4, 5] != 20)
+    abort ();
+  s[]++;
+  s[42]++;
+  ++s[3, 2];
+  if (s.a[0] != 65 || s.a[42] != 23 || s.a[19] != 46)
+    abort ();
+  T t;
+  U u;
+  if (&u[t] != &buf[42])
+    abort ();
+  if (&t[u] != &buf[42])
+    abort ();
+}
--- gcc/testsuite/g++.dg/cpp23/subscript2.C.jj	2021-10-13 16:12:32.811422262 +0200
+++ gcc/testsuite/g++.dg/cpp23/subscript2.C	2021-10-13 16:30:59.199509921 +0200
@@ -0,0 +1,51 @@ 
+// P2128R6
+// { dg-do compile }
+// { dg-options "-std=c++23" }
+
+struct S
+{
+  S () : a {} {};
+  int &operator[] () { return a[0]; }
+  int &operator[] (int x) { return a[x]; }
+  int &operator[] (int x, long y) { return a[x + y * 8]; }
+  int a[64];
+};
+
+struct T
+{
+  operator int () { return 42; };
+};
+
+int buf[64];
+
+struct U
+{
+  operator int * () { return buf; }
+};
+
+struct V
+{
+  V () : a {} {};
+  V (int x, int y, int z) : a {x, y, z} {};
+  int &operator[] () { return a[0]; }				// { dg-message "candidate" }
+  int &operator[] (int x, long y) { return a[x + y * 8]; }	// { dg-message "candidate" }
+  int a[64];
+};
+
+void
+foo ()
+{
+  S s;
+  T t;
+  U u;
+  V v;
+  auto &a = buf[];		// { dg-error "built-in subscript operator without expression list" }
+  auto &b = buf[1, 2];		// { dg-warning "top-level comma expression in array subscript changed meaning in" }
+  auto &c = s[1, 2, 3];		// { dg-warning "top-level comma expression in array subscript changed meaning in" }
+  auto &d = v[1];		// { dg-error "no match for 'operator\\\[\\\]' in 'v\\\[1\\\]' \\\(operand types are 'V' and 'int'\\\)" }
+  auto &e = v[1, 2, 3];		// { dg-error "no match for call to 'V::operator\\\[\\\] \\\(int, int, int\\\)'" }
+  auto &f = t[42, u];		// { dg-warning "top-level comma expression in array subscript changed meaning in" }
+  auto &g = u[42, t];		// { dg-warning "top-level comma expression in array subscript changed meaning in" }
+  auto &h = buf[42, 2.5];	// { dg-warning "top-level comma expression in array subscript changed meaning in" }
+				// { dg-error "invalid types \[^\n\r]* for array subscript" "" { target *-*-* } .-1 }
+}
--- gcc/testsuite/g++.dg/cpp23/subscript3.C.jj	2021-10-13 19:37:10.328203173 +0200
+++ gcc/testsuite/g++.dg/cpp23/subscript3.C	2021-10-13 19:40:07.297662178 +0200
@@ -0,0 +1,90 @@ 
+// P2128R6
+// { dg-do run }
+// { dg-options "-std=c++23" }
+
+extern "C" void abort ();
+
+struct S
+{
+  constexpr S () : a {} {};
+  constexpr S (int x, int y, int z) : a {x, y, z} {};
+  constexpr int &operator[] () { return a[0]; }
+  constexpr int &operator[] (int x) { return a[x]; }
+  constexpr int &operator[] (int x, long y) { return a[x + y * 8]; }
+  int a[64];
+};
+
+struct T
+{
+  operator int () { return 42; };
+};
+
+int buf[64];
+
+struct U
+{
+  operator int * () { return buf; }
+};
+
+template <int N>
+void
+foo ()
+{
+  static_assert (S ()[1] == 0);
+  static_assert (S (1, 2, 42)[2] == 42);
+  static_assert (S ()[3, 4] == 0);
+  static_assert (S (1, 43, 2)[1, 0] == 43);
+  static_assert (S ()[] == 0);
+  static_assert (S (44, 1, 2)[] == 44);
+  S s;
+  for (int i = 0; i < 64; i++)
+    s.a[i] = 64 - i;
+  if (s[] != 64 || s[3] != 61 || s[4, 5] != 20)
+    abort ();
+  s[]++;
+  s[42]++;
+  ++s[3, 2];
+  if (s.a[0] != 65 || s.a[42] != 23 || s.a[19] != 46)
+    abort ();
+  T t;
+  U u;
+  if (&u[t] != &buf[42])
+    abort ();
+  if (&t[u] != &buf[42])
+    abort ();
+}
+
+template <typename V, typename W, typename X>
+void
+bar ()
+{
+  static_assert (V ()[1] == 0);
+  static_assert (V (1, 2, 42)[2] == 42);
+  static_assert (V ()[3, 4] == 0);
+  static_assert (V (1, 43, 2)[1, 0] == 43);
+  static_assert (V ()[] == 0);
+  static_assert (V (44, 1, 2)[] == 44);
+  V s;
+  for (int i = 0; i < 64; i++)
+    s.a[i] = 64 - i;
+  if (s[] != 64 || s[3] != 61 || s[4, 5] != 20)
+    abort ();
+  s[]++;
+  s[42]++;
+  ++s[3, 2];
+  if (s.a[0] != 65 || s.a[42] != 23 || s.a[19] != 46)
+    abort ();
+  W t;
+  X u;
+  if (&u[t] != &buf[42])
+    abort ();
+  if (&t[u] != &buf[42])
+    abort ();
+}
+
+int
+main ()
+{
+  foo <0> ();
+  bar <S, T, U> ();
+}
--- gcc/testsuite/g++.dg/cpp23/subscript4.C.jj	2021-10-13 19:41:26.899519224 +0200
+++ gcc/testsuite/g++.dg/cpp23/subscript4.C	2021-10-13 19:54:11.402539793 +0200
@@ -0,0 +1,44 @@ 
+// P2128R6
+// { dg-do run }
+// { dg-options "-std=c++23" }
+
+extern "C" void abort ();
+
+struct S
+{
+  constexpr S () : a {} {};
+  constexpr S (int x, int y, int z) : a {x, y, z} {};
+  constexpr int &operator[] () { return a[0]; }
+  constexpr int &operator[] (int x) { return a[x]; }
+  constexpr int &operator[] (int x, long y) { return a[x + y * 8]; }
+  int a[64];
+};
+int buf[26];
+
+template <class ...Ts>
+auto &
+foo (S &s, Ts... args)
+{
+  return s[args...];
+}
+
+template <typename T, class ...Ts>
+auto &
+bar (T &s, Ts... args)
+{
+  return s[args...];
+}
+
+int
+main ()
+{
+  S s;
+  if (&foo (s) != &s.a[0]
+      || &foo (s, 42) != &s.a[42]
+      || &foo (s, 5, 4) != &s.a[37]
+      || &bar (s) != &s.a[0]
+      || &bar (s, 22) != &s.a[22]
+      || &bar (s, 17, 3L) != &s.a[41]
+      || &bar (buf, 5) != &buf[5])
+    abort ();
+}
--- gcc/testsuite/g++.dg/cpp23/subscript5.C.jj	2021-10-14 10:09:30.316991001 +0200
+++ gcc/testsuite/g++.dg/cpp23/subscript5.C	2021-10-14 10:08:54.357511795 +0200
@@ -0,0 +1,28 @@ 
+// P2128R6
+// { dg-do run { target c++11 } }
+
+#include <initializer_list>
+#include <cstdlib>
+
+struct S
+{
+  S () : a {} {};
+  int &operator[] (std::initializer_list<int> l) {
+    int sum = 0;
+    for (auto x : l)
+      sum += x;
+    return a[sum];
+  }
+  int a[64];
+};
+
+int
+main ()
+{
+  S s;
+  if (&s[{0}] != &s.a[0]
+      || &s[{42}] != &s.a[42]
+      || &s[{5, 7, 9}] != &s.a[5 + 7 + 9]
+      || &s[{1, 2, 3, 4}] != &s.a[1 + 2 + 3 + 4])
+    abort ();
+}
--- gcc/testsuite/g++.dg/cpp23/subscript6.C.jj	2021-10-14 10:11:34.964185752 +0200
+++ gcc/testsuite/g++.dg/cpp23/subscript6.C	2021-10-14 10:11:29.545264236 +0200
@@ -0,0 +1,31 @@ 
+// P2128R6
+// { dg-do run }
+// { dg-options "-std=c++23" }
+
+#include <initializer_list>
+#include <cstdlib>
+
+struct S
+{
+  S () : a {} {};
+  int &operator[] (std::initializer_list<int> l, std::initializer_list<int> m) {
+    int sum = 0;
+    for (auto x : l)
+      sum += x;
+    for (auto x : m)
+      sum += x;
+    return a[sum];
+  }
+  int a[64];
+};
+
+int
+main ()
+{
+  S s;
+  if (&s[{0}, {3, 1, 2}] != &s.a[0 + 3 + 1 + 2]
+      || &s[{42}, {11, 1}] != &s.a[42 + 11 + 1]
+      || &s[{5, 7, 9}, {3}] != &s.a[5 + 7 + 9 + 3]
+      || &s[{1, 2, 3, 4}, {3, 5, 8}] != &s.a[1 + 2 + 3 + 4 + 3 + 5 + 8])
+    abort ();
+}