c++: Implement P2662R3, Pack Indexing [PR113798]

Message ID 20241022192217.508500-1-polacek@redhat.com
State New
Headers
Series c++: Implement P2662R3, Pack Indexing [PR113798] |

Checks

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

Commit Message

Marek Polacek Oct. 22, 2024, 7:22 p.m. UTC
  Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
This patch implements C++26 Pack Indexing, as described in
<https://wg21.link/P2662R3>.

The issue discussing how to mangle pack indexes has not been resolved
yet <https://github.com/itanium-cxx-abi/cxx-abi/issues/175> and I've
made no attempt to address it so far.

Rather than introducing a new template code for a pack indexing, I'm
adding a new operand to EXPR_PACK_EXPANSION to store the index; for
TYPE_PACK_EXPANSION, I'm stashing the index into TYPE_VALUES_RAW.  This
feature is akin to __type_pack_element, so they can share the element
extraction part.

A pack indexing in a decltype proved to be a bit tricky; eventually,
I've added PACK_EXPANSION_PARENTHESIZED_P -- while parsing, we can't
really tell what it's going to expand to.

With this feature, it's valid to write something like

  using U = tmpl<Ts...[Is]...>;

where we first expand the template argument into

  Ts...[Is#0], Ts...[Is#1], ...

and then substitute each individual pack index.

I have no test for the module.cc code, that is just guesswork.

	PR c++/113798

gcc/cp/ChangeLog:

	* cp-tree.def (EXPR_PACK_EXPANSION): Add another operand.
	* cp-tree.h (PACK_EXPANSION_INDEX): Define.
	(PACK_EXPANSION_PARENTHESIZED_P): Define.
	(pack_index_element): Declare.
	* cxx-pretty-print.cc (cxx_pretty_printer::expression)
	<case EXPR_PACK_EXPANSION>: Print PACK_EXPANSION_INDEX.
	(cxx_pretty_printer::type_id) <case TYPE_PACK_EXPANSION>: Print
	PACK_EXPANSION_INDEX.
	* decl.cc (xref_basetypes): Set PACK_EXPANSION_INDEX.
	* error.cc (dump_type): Print PACK_EXPANSION_INDEX.
	* mangle.cc (write_type) <case TYPE_PACK_EXPANSION>: New comment.
	* module.cc (trees_out::type_node): Stream PACK_EXPANSION_INDEX.
	(trees_in::tree_node): Read in PACK_EXPANSION_INDEX.
	* parser.cc (cp_parser_pack_index): New.
	(cp_parser_primary_expression): Handle a pack-index-expression.
	(cp_parser_unqualified_id): Handle a pack-index-specifier.
	(cp_parser_nested_name_specifier_opt): Also check for
	a pack-index-specifier.  Handle a pack-index-specifier.
	(cp_parser_mem_initializer_id): Handle a pack-index-specifier.
	(cp_parser_simple_type_specifier): Likewise.
	(cp_parser_base_specifier): Likewise.
	* pt.cc (iterative_hash_template_arg) <case TYPE_PACK_EXPANSION>: Also
	hash PACK_EXPANSION_INDEX.
	(find_parameter_packs_r) <case VAR_DECL>: A type with
	PACK_EXPANSION_INDEX is not a bare parameter pack.
	<case TYPE_PACK_EXPANSION>: Walk into PACK_EXPANSION_INDEXes.
	(instantiate_class_template): Handle a pack-index-specifier.
	(tsubst_pack_expansion): tsubst_expr the PACK_EXPANSION_INDEX.
	If there was a PACK_EXPANSION_INDEX, pull out the element via
	pack_index_element.
	(tsubst) <case TYPENAME_TYPE>: Handle a pack-index-specifier.
	<case DECLTYPE_TYPE>: For a PACK_EXPANSION_P, figure out if it should
	be treated as an id-expression.
	<case TYPE_PACK_EXPANSION>: Handle it if there is a
	PACK_EXPANSION_INDEX.
	(tsubst_stmt) <case EXPR_PACK_EXPANSION>: Likewise.
	(tsubst_expr) <case EXPR_PACK_EXPANSION>: Likewise.
	(tsubst_initializer_list): Handle a pack-index-specifier.
	* ptree.cc (cxx_print_type) <case TYPE_PACK_EXPANSION>: Print
	the PACK_EXPANSION_INDEX.
	* semantics.cc (finish_parenthesized_expr): Maybe set
	PACK_EXPANSION_PARENTHESIZED_P.
	(finish_base_specifier): Check for a PACK_EXPANSION_P with
	a PACK_EXPANSION_INDEX.
	(get_vec_elt_checking): New, broken out of finish_type_pack_element.
	(finish_type_pack_element): Call get_vec_elt_checking.
	(pack_index_element): New.
	* tree.cc (cp_build_qualified_type): Set PACK_EXPANSION_INDEX.
	(cp_tree_equal) <case EXPR_PACK_EXPANSION>: Also compare the
	PACK_EXPANSION_INDEXes.
	(cp_walk_subtrees) <case TYPE_PACK_EXPANSION,
	case EXPR_PACK_EXPANSION>: Walk the PACK_EXPANSION_INDEX.
	* typeck.cc (structural_comptypes) <case TYPE_PACK_EXPANSION>: Also
	compare the PACK_EXPANSION_INDEXes.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp26/pack-indexing1.C: New test.
	* g++.dg/cpp26/pack-indexing2.C: New test.
	* g++.dg/cpp26/pack-indexing3.C: New test.
	* g++.dg/cpp26/pack-indexing4.C: New test.
	* g++.dg/cpp26/pack-indexing5.C: New test.
---
 gcc/cp/cp-tree.def                          |   2 +-
 gcc/cp/cp-tree.h                            |  13 ++
 gcc/cp/cxx-pretty-print.cc                  |  12 ++
 gcc/cp/decl.cc                              |   8 +-
 gcc/cp/error.cc                             |   6 +
 gcc/cp/mangle.cc                            |   2 +
 gcc/cp/module.cc                            |   3 +
 gcc/cp/parser.cc                            | 144 +++++++++++++++++---
 gcc/cp/pt.cc                                |  90 +++++++++---
 gcc/cp/ptree.cc                             |   1 +
 gcc/cp/semantics.cc                         |  50 ++++++-
 gcc/cp/tree.cc                              |  14 +-
 gcc/cp/typeck.cc                            |   4 +-
 gcc/testsuite/g++.dg/cpp26/pack-indexing1.C | 102 ++++++++++++++
 gcc/testsuite/g++.dg/cpp26/pack-indexing2.C | 111 +++++++++++++++
 gcc/testsuite/g++.dg/cpp26/pack-indexing3.C |  41 ++++++
 gcc/testsuite/g++.dg/cpp26/pack-indexing4.C |  65 +++++++++
 gcc/testsuite/g++.dg/cpp26/pack-indexing5.C |  41 ++++++
 18 files changed, 662 insertions(+), 47 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp26/pack-indexing1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp26/pack-indexing2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp26/pack-indexing3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp26/pack-indexing4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp26/pack-indexing5.C


base-commit: 5fd1c0c1b6968d55e3f997d67a4c149edf20c012
  

Comments

Jason Merrill Oct. 22, 2024, 11:42 p.m. UTC | #1
On 10/22/24 3:22 PM, Marek Polacek wrote:
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> -- >8 --
> This patch implements C++26 Pack Indexing, as described in
> <https://wg21.link/P2662R3>.

Great!

> The issue discussing how to mangle pack indexes has not been resolved
> yet <https://github.com/itanium-cxx-abi/cxx-abi/issues/175> and I've
> made no attempt to address it so far.
> 
> Rather than introducing a new template code for a pack indexing, I'm
> adding a new operand to EXPR_PACK_EXPANSION to store the index; for
> TYPE_PACK_EXPANSION, I'm stashing the index into TYPE_VALUES_RAW.  This
> feature is akin to __type_pack_element, so they can share the element
> extraction part.
> 
> A pack indexing in a decltype proved to be a bit tricky; eventually,
> I've added PACK_EXPANSION_PARENTHESIZED_P -- while parsing, we can't
> really tell what it's going to expand to.

As I comment below, I think we should have enough information while 
parsing; what it expands to doesn't matter.

> With this feature, it's valid to write something like
> 
>    using U = tmpl<Ts...[Is]...>;
> 
> where we first expand the template argument into
> 
>    Ts...[Is#0], Ts...[Is#1], ...
> 
> and then substitute each individual pack index.
> 
> I have no test for the module.cc code, that is just guesswork.

Looks straightforward enough.

> @@ -2605,6 +2605,8 @@ write_type (tree type)
>              case TYPE_PACK_EXPANSION:
>                write_string ("Dp");
>                write_type (PACK_EXPANSION_PATTERN (type));
> +	      /* TODO: Mangle PACK_EXPANSION_INDEX
> +		 <https://github.com/itanium-cxx-abi/cxx-abi/issues/175>  */

Could we warn about this so it doesn't get forgotten?  And similarly in 
write_expression?

> @@ -3952,7 +3953,11 @@ find_parameter_packs_r (tree *tp, int *walk_subtrees, void* data)
>         break;
>   
>       case VAR_DECL:
> -      if (DECL_PACK_P (t))
> +      /* We can have
> +	   T...[0] a;
> +	   (T...[0])(a); // #1
> +	 where the 'a' in #1 is not a bare parameter pack.  */
> +      if (DECL_PACK_P (t) && !PACK_EXPANSION_INDEX (TREE_TYPE (t)))

Seems like the INDEX check should move into DECL_PACK_P?

Why doesn't this apply to PARM_DECL above?

> @@ -13946,6 +13969,10 @@ tsubst_pack_expansion (tree t, tree args, tsubst_flags_t complain,
>         && PACK_EXPANSION_P (TREE_VEC_ELT (result, 0)))
>       return TREE_VEC_ELT (result, 0);
>   
> +  /* C++26 Pack Indexing.  */
> +  if (index)
> +    return pack_index_element (index, result, complain);

Could we only compute the desired element rather than computing all of 
them and selecting the desired one?

> @@ -16897,17 +16924,23 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>   	    ctx = tsubst_pack_expansion (ctx, args,
>   					 complain | tf_qualifying_scope,
>   					 in_decl);
> -	    if (ctx == error_mark_node
> -		|| TREE_VEC_LENGTH (ctx) > 1)
> +	    if (ctx == error_mark_node)
>   	      return error_mark_node;
> -	    if (TREE_VEC_LENGTH (ctx) == 0)
> +	    /* If there was a pack-index-specifier, we won't get a TREE_VEC,
> +	       just the single element.  */
> +	    if (TREE_CODE (ctx) == TREE_VEC)
>   	      {
> -		if (complain & tf_error)
> -		  error ("%qD is instantiated for an empty pack",
> -			 TYPENAME_TYPE_FULLNAME (t));
> -		return error_mark_node;
> +		if (TREE_VEC_LENGTH (ctx) > 1)
> +		  return error_mark_node;

This is preexisting, but it seems like we're missing a call to error() 
in this case.

> @@ -17041,13 +17074,20 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>   	else
>   	  {
>   	    bool id = DECLTYPE_TYPE_ID_EXPR_OR_MEMBER_ACCESS_P (t);
> -	    if (id && TREE_CODE (DECLTYPE_TYPE_EXPR (t)) == BIT_NOT_EXPR
> -		&& EXPR_P (type))
> +	    tree op = DECLTYPE_TYPE_EXPR (t);
> +	    if (id && TREE_CODE (op) == BIT_NOT_EXPR && EXPR_P (type))
>   	      /* In a template ~id could be either a complement expression
>   		 or an unqualified-id naming a destructor; if instantiating
>   		 it produces an expression, it's not an id-expression or
>   		 member access.  */
>   	      id = false;
> +	    /* With pack indexing, we don't know what it's going to expand to
> +	       until instantiation.  The intent is that a pack indexing
> +	       expression behaves exactly as the underlying expression
> +	       would.  */
> +	    else if (PACK_EXPANSION_P (op))
> +	      id = (!PACK_EXPANSION_PARENTHESIZED_P (op)
> +		    && unparenthesized_id_or_class_member_access_p (type));

Why isn't the value of DECLTYPE_TYPE_ID_EXPR_OR_MEMBER_ACCESS_P already 
accurate in this case?  It seems to me that 'id' should be based on the 
syntax of the decltype, not what the substitution produces.

And so maybe we don't need PACK_EXPANSION_PARENTHESIZED_P except maybe 
for decltype(auto)?

> @@ -17074,6 +17114,11 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>       case NONTYPE_ARGUMENT_PACK:
>         return tsubst_argument_pack (t, args, complain, in_decl);
>   
> +    case TYPE_PACK_EXPANSION:
> +      if (PACK_EXPANSION_INDEX (t))
> +	return tsubst_pack_expansion (t, args, complain, in_decl);
> +      gcc_fallthrough ();

Let's gcc_unreachable here rather than fallthrough to unreachable.

> @@ -19596,6 +19641,8 @@ tsubst_stmt (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>         }
>   
>       case EXPR_PACK_EXPANSION:
> +      if (PACK_EXPANSION_INDEX (t))
> +	RETURN (tsubst_pack_expansion (t, args, complain, in_decl));
>         error ("invalid use of pack expansion expression");
>         RETURN (error_mark_node);
>   
> @@ -21776,6 +21823,8 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>         }
>   
>       case EXPR_PACK_EXPANSION:
> +      if (PACK_EXPANSION_INDEX (t))
> +	RETURN (tsubst_pack_expansion (t, args, complain, in_decl));
>         error ("invalid use of pack expansion expression");
>         RETURN (error_mark_node);

Preexisting, but do we need this case in both _stmt and _expr?

> @@ -27685,6 +27734,14 @@ tsubst_initializer_list (tree t, tree argvec)
>                                                    NULL_TREE);
>             if (expanded_bases == error_mark_node)
>               continue;
> +	  /* If there was a pack-index-specifier, we won't get
> +	     a TREE_VEC but the rest of the code assumes so.  */

Hmm, this difference in return value seems fragile, but I guess you only 
actually need to compensate for it in a couple of places...

> @@ -4181,7 +4183,9 @@ finish_base_specifier (tree base, tree access, bool virtual_p)
>         error ("invalid base-class specification");
>         result = NULL_TREE;
>       }
> -  else if (! MAYBE_CLASS_TYPE_P (base))
> +  else if (! MAYBE_CLASS_TYPE_P (base)
> +	   && ! (PACK_EXPANSION_P (base)
> +		 && PACK_EXPANSION_INDEX (base)))

Instead of this change, this case should be added to WILDCARD_TYPE_P.

> -/* Implement the __type_pack_element keyword: Return the type
> -   at index IDX within TYPES.  */
> +/* Return the type at index IDX within TYPES.  */
>   
>   static tree
> -finish_type_pack_element (tree idx, tree types, tsubst_flags_t complain)
> +get_vec_elt_checking (tree idx, tree types, bool pack_index_p,
> +		      tsubst_flags_t complain)
>   {
>     idx = maybe_constant_value (idx);
>     if (TREE_CODE (idx) != INTEGER_CST || !INTEGRAL_TYPE_P (TREE_TYPE (idx)))
>       {
>         if (complain & tf_error)
> -	error ("%<__type_pack_element%> index is not an integral constant");
> +	{
> +	  if (pack_index_p)
> +	    error ("pack index is not an integral constant");
> +	  else
> +	    error ("%<__type_pack_element%> index is not an integral constant");

Maybe just always say "pack index"?

> @@ -5575,12 +5581,16 @@ cp_walk_subtrees (tree *tp, int *walk_subtrees_p, walk_tree_fn func,
>       case TYPE_PACK_EXPANSION:
>         WALK_SUBTREE (TREE_TYPE (t));
>         WALK_SUBTREE (PACK_EXPANSION_EXTRA_ARGS (t));
> +      if (PACK_EXPANSION_INDEX (t))
> +	WALK_SUBTREE (PACK_EXPANSION_INDEX (t));

I don't think it's necessary to check non-null before WALK_SUBTREE.

Jason
  
Patrick Palka Oct. 23, 2024, 2:20 p.m. UTC | #2
On Tue, 22 Oct 2024, Marek Polacek wrote:

> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> -- >8 --
> This patch implements C++26 Pack Indexing, as described in
> <https://wg21.link/P2662R3>.
> 
> The issue discussing how to mangle pack indexes has not been resolved
> yet <https://github.com/itanium-cxx-abi/cxx-abi/issues/175> and I've
> made no attempt to address it so far.
> 
> Rather than introducing a new template code for a pack indexing, I'm
> adding a new operand to EXPR_PACK_EXPANSION to store the index; for
> TYPE_PACK_EXPANSION, I'm stashing the index into TYPE_VALUES_RAW.  This

What are the pros and cons of reusing TYPE/EXPR_PACK_EXPANSION instead
of creating two new tree codes for these operators (one of whose
operands would itself be a bare TYPE/EXPR_PACK_EXPANSION)?

I feel a little iffy at first glance about reusing these tree codes
since it muddles what "kind" of tree they are: currently they represent
a _vector_ or types/exprs (which is reflected by their tcc_exceptional
class), and with this approach they can now also represent a single
type/expr (despite their tcc_exceptional class), depending on whether
PACK_EXPANSION_INDEX is set.

At the same time, the pattern of a generic *_PACK_EXPANSION can be
anything whereas for these index operators we know it's always a single
bare pack, so we also don't need the full expressivity of
*_PACK_EXPANSION to represent these operators either.

Maybe it's the case that reusing these tree codes significantly
simplifies parts of the implementation?

> feature is akin to __type_pack_element, so they can share the element
> extraction part.
> 
> A pack indexing in a decltype proved to be a bit tricky; eventually,
> I've added PACK_EXPANSION_PARENTHESIZED_P -- while parsing, we can't
> really tell what it's going to expand to.
> 
> With this feature, it's valid to write something like
> 
>   using U = tmpl<Ts...[Is]...>;
> 
> where we first expand the template argument into
> 
>   Ts...[Is#0], Ts...[Is#1], ...
> 
> and then substitute each individual pack index.
> 
> I have no test for the module.cc code, that is just guesswork.
> 
> 	PR c++/113798
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-tree.def (EXPR_PACK_EXPANSION): Add another operand.
> 	* cp-tree.h (PACK_EXPANSION_INDEX): Define.
> 	(PACK_EXPANSION_PARENTHESIZED_P): Define.
> 	(pack_index_element): Declare.
> 	* cxx-pretty-print.cc (cxx_pretty_printer::expression)
> 	<case EXPR_PACK_EXPANSION>: Print PACK_EXPANSION_INDEX.
> 	(cxx_pretty_printer::type_id) <case TYPE_PACK_EXPANSION>: Print
> 	PACK_EXPANSION_INDEX.
> 	* decl.cc (xref_basetypes): Set PACK_EXPANSION_INDEX.
> 	* error.cc (dump_type): Print PACK_EXPANSION_INDEX.
> 	* mangle.cc (write_type) <case TYPE_PACK_EXPANSION>: New comment.
> 	* module.cc (trees_out::type_node): Stream PACK_EXPANSION_INDEX.
> 	(trees_in::tree_node): Read in PACK_EXPANSION_INDEX.
> 	* parser.cc (cp_parser_pack_index): New.
> 	(cp_parser_primary_expression): Handle a pack-index-expression.
> 	(cp_parser_unqualified_id): Handle a pack-index-specifier.
> 	(cp_parser_nested_name_specifier_opt): Also check for
> 	a pack-index-specifier.  Handle a pack-index-specifier.
> 	(cp_parser_mem_initializer_id): Handle a pack-index-specifier.
> 	(cp_parser_simple_type_specifier): Likewise.
> 	(cp_parser_base_specifier): Likewise.
> 	* pt.cc (iterative_hash_template_arg) <case TYPE_PACK_EXPANSION>: Also
> 	hash PACK_EXPANSION_INDEX.
> 	(find_parameter_packs_r) <case VAR_DECL>: A type with
> 	PACK_EXPANSION_INDEX is not a bare parameter pack.
> 	<case TYPE_PACK_EXPANSION>: Walk into PACK_EXPANSION_INDEXes.
> 	(instantiate_class_template): Handle a pack-index-specifier.
> 	(tsubst_pack_expansion): tsubst_expr the PACK_EXPANSION_INDEX.
> 	If there was a PACK_EXPANSION_INDEX, pull out the element via
> 	pack_index_element.
> 	(tsubst) <case TYPENAME_TYPE>: Handle a pack-index-specifier.
> 	<case DECLTYPE_TYPE>: For a PACK_EXPANSION_P, figure out if it should
> 	be treated as an id-expression.
> 	<case TYPE_PACK_EXPANSION>: Handle it if there is a
> 	PACK_EXPANSION_INDEX.
> 	(tsubst_stmt) <case EXPR_PACK_EXPANSION>: Likewise.
> 	(tsubst_expr) <case EXPR_PACK_EXPANSION>: Likewise.
> 	(tsubst_initializer_list): Handle a pack-index-specifier.
> 	* ptree.cc (cxx_print_type) <case TYPE_PACK_EXPANSION>: Print
> 	the PACK_EXPANSION_INDEX.
> 	* semantics.cc (finish_parenthesized_expr): Maybe set
> 	PACK_EXPANSION_PARENTHESIZED_P.
> 	(finish_base_specifier): Check for a PACK_EXPANSION_P with
> 	a PACK_EXPANSION_INDEX.
> 	(get_vec_elt_checking): New, broken out of finish_type_pack_element.
> 	(finish_type_pack_element): Call get_vec_elt_checking.
> 	(pack_index_element): New.
> 	* tree.cc (cp_build_qualified_type): Set PACK_EXPANSION_INDEX.
> 	(cp_tree_equal) <case EXPR_PACK_EXPANSION>: Also compare the
> 	PACK_EXPANSION_INDEXes.
> 	(cp_walk_subtrees) <case TYPE_PACK_EXPANSION,
> 	case EXPR_PACK_EXPANSION>: Walk the PACK_EXPANSION_INDEX.
> 	* typeck.cc (structural_comptypes) <case TYPE_PACK_EXPANSION>: Also
> 	compare the PACK_EXPANSION_INDEXes.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp26/pack-indexing1.C: New test.
> 	* g++.dg/cpp26/pack-indexing2.C: New test.
> 	* g++.dg/cpp26/pack-indexing3.C: New test.
> 	* g++.dg/cpp26/pack-indexing4.C: New test.
> 	* g++.dg/cpp26/pack-indexing5.C: New test.
> ---
>  gcc/cp/cp-tree.def                          |   2 +-
>  gcc/cp/cp-tree.h                            |  13 ++
>  gcc/cp/cxx-pretty-print.cc                  |  12 ++
>  gcc/cp/decl.cc                              |   8 +-
>  gcc/cp/error.cc                             |   6 +
>  gcc/cp/mangle.cc                            |   2 +
>  gcc/cp/module.cc                            |   3 +
>  gcc/cp/parser.cc                            | 144 +++++++++++++++++---
>  gcc/cp/pt.cc                                |  90 +++++++++---
>  gcc/cp/ptree.cc                             |   1 +
>  gcc/cp/semantics.cc                         |  50 ++++++-
>  gcc/cp/tree.cc                              |  14 +-
>  gcc/cp/typeck.cc                            |   4 +-
>  gcc/testsuite/g++.dg/cpp26/pack-indexing1.C | 102 ++++++++++++++
>  gcc/testsuite/g++.dg/cpp26/pack-indexing2.C | 111 +++++++++++++++
>  gcc/testsuite/g++.dg/cpp26/pack-indexing3.C |  41 ++++++
>  gcc/testsuite/g++.dg/cpp26/pack-indexing4.C |  65 +++++++++
>  gcc/testsuite/g++.dg/cpp26/pack-indexing5.C |  41 ++++++
>  18 files changed, 662 insertions(+), 47 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp26/pack-indexing1.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp26/pack-indexing2.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp26/pack-indexing3.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp26/pack-indexing4.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp26/pack-indexing5.C
> 
> diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def
> index 18f75108c7b..576765667d0 100644
> --- a/gcc/cp/cp-tree.def
> +++ b/gcc/cp/cp-tree.def
> @@ -395,7 +395,7 @@ DEFTREECODE (TYPE_PACK_EXPANSION, "type_pack_expansion", tcc_type, 0)
>  
>     EXPR_PACK_EXPANSION plays precisely the same role as TYPE_PACK_EXPANSION,
>     but will be used for expressions.  */
> -DEFTREECODE (EXPR_PACK_EXPANSION, "expr_pack_expansion", tcc_expression, 3)
> +DEFTREECODE (EXPR_PACK_EXPANSION, "expr_pack_expansion", tcc_expression, 4)
>  
>  /* Selects the Ith parameter out of an argument pack. This node will
>     be used when instantiating pack expansions; see
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index a44100a2bc4..12472d95247 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -510,6 +510,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
>        OVL_LOOKUP_P (in OVERLOAD)
>        LOOKUP_FOUND_P (in RECORD_TYPE, UNION_TYPE, ENUMERAL_TYPE, NAMESPACE_DECL)
>        FNDECL_MANIFESTLY_CONST_EVALUATED (in FUNCTION_DECL)
> +      PACK_EXPANSION_PARENTHESIZED_P (in *_PACK_EXPANSION)
>     5: IDENTIFIER_VIRTUAL_P (in IDENTIFIER_NODE)
>        FUNCTION_RVALUE_QUALIFIED (in FUNCTION_TYPE, METHOD_TYPE)
>        CALL_EXPR_REVERSE_ARGS (in CALL_EXPR, AGGR_INIT_EXPR)
> @@ -4025,6 +4026,12 @@ struct GTY(()) lang_decl {
>      ? &TYPE_MAX_VALUE_RAW (NODE)			\
>      : &TREE_OPERAND ((NODE), 2))
>  
> +/* For a pack-index T...[N], the index N.  */
> +#define PACK_EXPANSION_INDEX(NODE) \
> +  *(TREE_CODE (PACK_EXPANSION_CHECK (NODE)) == TYPE_PACK_EXPANSION \
> +    ? &TYPE_VALUES_RAW (NODE)			\
> +    : &TREE_OPERAND ((NODE), 3))
> +
>  /* True iff this pack expansion is within a function context.  */
>  #define PACK_EXPANSION_LOCAL_P(NODE) \
>    TREE_LANG_FLAG_0 (PACK_EXPANSION_CHECK (NODE))
> @@ -4042,6 +4049,11 @@ struct GTY(()) lang_decl {
>  #define PACK_EXPANSION_FORCE_EXTRA_ARGS_P(NODE) \
>    TREE_LANG_FLAG_3 (PACK_EXPANSION_CHECK (NODE))
>  
> +/* Indicates whether a pack expansion has been parenthesized.  Used for
> +   a pack expansion in a decltype.  */
> +#define PACK_EXPANSION_PARENTHESIZED_P(NODE) \
> +  TREE_LANG_FLAG_4 (PACK_EXPANSION_CHECK (NODE))
> +
>  /* True iff the wildcard can match a template parameter pack.  */
>  #define WILDCARD_PACK_P(NODE) TREE_LANG_FLAG_0 (NODE)
>  
> @@ -7906,6 +7918,7 @@ extern tree finish_underlying_type	        (tree);
>  extern tree calculate_bases                     (tree, tsubst_flags_t);
>  extern tree finish_bases                        (tree, bool);
>  extern tree calculate_direct_bases              (tree, tsubst_flags_t);
> +extern tree pack_index_element			(tree, tree, tsubst_flags_t);
>  extern tree finish_offsetof			(tree, tree, location_t);
>  extern void finish_decl_cleanup			(tree, tree);
>  extern void finish_eh_cleanup			(tree);
> diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
> index 41e6bdfdda5..47111195f72 100644
> --- a/gcc/cp/cxx-pretty-print.cc
> +++ b/gcc/cp/cxx-pretty-print.cc
> @@ -1216,6 +1216,12 @@ cxx_pretty_printer::expression (tree t)
>      case EXPR_PACK_EXPANSION:
>        expression (PACK_EXPANSION_PATTERN (t));
>        pp_cxx_ws_string (this, "...");
> +      if (PACK_EXPANSION_INDEX (t))
> +	{
> +	  pp_cxx_left_bracket (this);
> +	  expression (PACK_EXPANSION_INDEX (t));
> +	  pp_cxx_right_bracket (this);
> +	}
>        break;
>  
>      case UNARY_LEFT_FOLD_EXPR:
> @@ -1916,6 +1922,12 @@ cxx_pretty_printer::type_id (tree t)
>      case TYPE_PACK_EXPANSION:
>        type_id (PACK_EXPANSION_PATTERN (t));
>        pp_cxx_ws_string (this, "...");
> +      if (PACK_EXPANSION_INDEX (t))
> +	{
> +	  pp_cxx_left_bracket (this);
> +	  expression (PACK_EXPANSION_INDEX (t));
> +	  pp_cxx_right_bracket (this);
> +	}
>        break;
>  
>      case TYPE_ARGUMENT_PACK:
> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> index 7281818be8f..311f4ad6cd1 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -17040,8 +17040,12 @@ xref_basetypes (tree ref, tree base_list)
>  	}
>  
>        if (PACK_EXPANSION_P (TREE_VALUE (base_list)))
> -        /* Regenerate the pack expansion for the bases. */
> -        basetype = make_pack_expansion (basetype);
> +	{
> +	  /* Regenerate the pack expansion for the bases. */
> +	  basetype = make_pack_expansion (basetype);
> +	  PACK_EXPANSION_INDEX (basetype)
> +	    = PACK_EXPANSION_INDEX (TREE_VALUE (base_list));
> +	}
>  
>        TYPE_MARKED_P (basetype) = 1;
>  
> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> index 65f70c595cf..33f964a11bd 100644
> --- a/gcc/cp/error.cc
> +++ b/gcc/cp/error.cc
> @@ -818,6 +818,12 @@ dump_type (cxx_pretty_printer *pp, tree t, int flags)
>      case TYPE_PACK_EXPANSION:
>        dump_type (pp, PACK_EXPANSION_PATTERN (t), flags);
>        pp_cxx_ws_string (pp, "...");
> +      if (PACK_EXPANSION_INDEX (t))
> +	{
> +	  pp_cxx_left_bracket (pp);
> +	  dump_expr (pp, PACK_EXPANSION_INDEX (t), flags & ~TFF_EXPR_IN_PARENS);
> +	  pp_cxx_right_bracket (pp);
> +	}
>        break;
>  
>      case TYPE_ARGUMENT_PACK:
> diff --git a/gcc/cp/mangle.cc b/gcc/cp/mangle.cc
> index 17988d69e1e..8c8a89897c9 100644
> --- a/gcc/cp/mangle.cc
> +++ b/gcc/cp/mangle.cc
> @@ -2605,6 +2605,8 @@ write_type (tree type)
>              case TYPE_PACK_EXPANSION:
>                write_string ("Dp");
>                write_type (PACK_EXPANSION_PATTERN (type));
> +	      /* TODO: Mangle PACK_EXPANSION_INDEX
> +		 <https://github.com/itanium-cxx-abi/cxx-abi/issues/175>  */
>                break;
>  
>              case DECLTYPE_TYPE:
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index fd9b1d3bf2e..da3e2b71a66 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -9207,6 +9207,7 @@ trees_out::type_node (tree type)
>  	u (PACK_EXPANSION_LOCAL_P (type));
>        tree_node (PACK_EXPANSION_PARAMETER_PACKS (type));
>        tree_node (PACK_EXPANSION_EXTRA_ARGS (type));
> +      tree_node (PACK_EXPANSION_INDEX (type));
>        break;
>  
>      case TYPENAME_TYPE:
> @@ -9763,6 +9764,7 @@ trees_in::tree_node (bool is_use)
>  	      bool local = u ();
>  	      tree param_packs = tree_node ();
>  	      tree extra_args = tree_node ();
> +	      tree index = tree_node ();
>  	      if (!get_overrun ())
>  		{
>  		  tree expn = cxx_make_type (TYPE_PACK_EXPANSION);
> @@ -9771,6 +9773,7 @@ trees_in::tree_node (bool is_use)
>  		  PACK_EXPANSION_PARAMETER_PACKS (expn) = param_packs;
>  		  PACK_EXPANSION_EXTRA_ARGS (expn) = extra_args;
>  		  PACK_EXPANSION_LOCAL_P (expn) = local;
> +		  PACK_EXPANSION_INDEX (expn) = index;
>  		  res = expn;
>  		}
>  	    }
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index 9d31a975dcf..7a054fb6c05 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -5739,6 +5739,56 @@ cp_parser_fold_expression (cp_parser *parser, tree expr1)
>    return finish_binary_fold_expr (loc, expr1, expr2, op);
>  }
>  
> +/*  Parse a pack-index-specifier:
> +
> +    pack-index-specifier:
> +      typedef-name ... [ constant-expression ]
> +
> +    or a pack-index-expression:
> +
> +    pack-index-expression:
> +      id-expression ... [ constant-expression ]
> +
> +    PACK is the parsed typedef-name or the id-expression.  Returns
> +    either an EXPR_PACK_EXPANSION or TYPE_PACK_EXPANSION.  */
> +
> +static tree
> +cp_parser_pack_index (cp_parser *parser, tree pack)
> +{
> +  if (cxx_dialect < cxx26)
> +    pedwarn (cp_lexer_peek_token (parser->lexer)->location,
> +	     OPT_Wc__26_extensions, "pack indexing only available with "
> +	     "%<-std=c++2c%> or %<-std=gnu++2c%>");
> +  /* Consume the '...' token.  */
> +  cp_lexer_consume_token (parser->lexer);
> +  /* Consume the '['.  */
> +  cp_lexer_consume_token (parser->lexer);
> +
> +  if (cp_lexer_next_token_is (parser->lexer, CPP_CLOSE_SQUARE))
> +    {
> +      error_at (cp_lexer_peek_token (parser->lexer)->location,
> +		"pack index missing");
> +      cp_lexer_consume_token (parser->lexer);
> +      return error_mark_node;
> +    }
> +
> +  tree expr = cp_parser_constant_expression (parser,
> +					     /*allow_non_constant_p=*/false,
> +					     /*non_constant_p=*/nullptr,
> +					     /*strict_p=*/true);
> +  /* Consume the ']'.  */
> +  cp_parser_require (parser, CPP_CLOSE_SQUARE, RT_CLOSE_SQUARE);
> +
> +  if (TREE_CODE (pack) == TYPE_DECL)
> +    pack = TREE_TYPE (pack);
> +  pack = make_pack_expansion (pack);
> +  if (pack == error_mark_node)
> +    return error_mark_node;
> +  PACK_EXPANSION_INDEX (pack) = expr;
> +
> +  return pack;
> +}
> +
>  /* Parse a primary-expression.
>  
>     primary-expression:
> @@ -6368,6 +6418,12 @@ cp_parser_primary_expression (cp_parser *parser,
>  	  = make_location (caret_loc, start_loc, finish_loc);
>  
>  	decl.set_location (combined_loc);
> +
> +	/* "T...[constant-expression]" is a C++26 pack-index-expression.  */
> +	if (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
> +	    && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SQUARE))
> +	  decl = cp_parser_pack_index (parser, decl);
> +
>  	return decl;
>        }
>  
> @@ -6411,6 +6467,7 @@ missing_template_diag (location_t loc, diagnostic_t diag_kind = DK_WARNING)
>     id-expression:
>       unqualified-id
>       qualified-id
> +     pack-index-expression
>  
>     qualified-id:
>       :: [opt] nested-name-specifier template [opt] unqualified-id
> @@ -6593,7 +6650,9 @@ cp_parser_id_expression (cp_parser *parser,
>       identifier
>       operator-function-id
>       conversion-function-id
> -     ~ class-name
> +     literal-operator-id
> +     ~ type-name
> +     ~ computed-type-specifier
>       template-id
>  
>     If TEMPLATE_KEYWORD_P is TRUE, we have just seen the `template'
> @@ -6900,6 +6959,14 @@ cp_parser_unqualified_id (cp_parser* parser,
>  		    "typedef-name %qD used as destructor declarator",
>  		    type_decl);
>  
> +	/* "~T...[N]" is a C++26 pack-index-specifier.  */
> +	if (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
> +	    && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SQUARE))
> +	  {
> +	    type_decl = cp_parser_pack_index (parser, type_decl);
> +	    return build_min_nt_loc (loc, BIT_NOT_EXPR, type_decl);
> +	  }
> +
>  	return build_min_nt_loc (loc, BIT_NOT_EXPR, TREE_TYPE (type_decl));
>        }
>  
> @@ -6970,9 +7037,10 @@ check_template_keyword_in_nested_name_spec (tree name)
>       class-or-namespace-name :: nested-name-specifier [opt]
>       class-or-namespace-name :: template nested-name-specifier [opt]
>  
> -   nested-name-specifier: [C++0x]
> +   nested-name-specifier: [C++11]
>       type-name ::
>       namespace-name ::
> +     computed-type-specifier ::
>       nested-name-specifier identifier ::
>       nested-name-specifier template [opt] simple-template-id ::
>  
> @@ -7080,6 +7148,10 @@ cp_parser_nested_name_specifier_opt (cp_parser *parser,
>  	    }
>  
>  	  if (token->type != CPP_SCOPE
> +	      /* See if a pack-index-specifier follows.  */
> +	      && !(token->type == CPP_ELLIPSIS
> +		   && cp_lexer_peek_nth_token (parser->lexer, 3)->type
> +		      == CPP_OPEN_SQUARE)
>  	      && !cp_parser_nth_token_starts_template_argument_list_p
>  		  (parser, 2))
>  	    break;
> @@ -7127,6 +7199,12 @@ cp_parser_nested_name_specifier_opt (cp_parser *parser,
>                                         check_dependency_p,
>                                         type_p,
>                                         is_declaration);
> +
> +      /* "T...[constant-expression]" is a C++26 pack-index-specifier.  */
> +      if (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
> +	  && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SQUARE))
> +      new_scope = cp_parser_pack_index (parser, new_scope);
> +
>        /* Look for the `::' token.  */
>        cp_parser_require (parser, CPP_SCOPE, RT_SCOPE);
>  
> @@ -17938,13 +18016,17 @@ cp_parser_mem_initializer (cp_parser* parser)
>  /* Parse a mem-initializer-id.
>  
>     mem-initializer-id:
> -     :: [opt] nested-name-specifier [opt] class-name
> -     decltype-specifier (C++11)
> +     class-or-decltype
>       identifier
>  
> +  class-or-decltype:
> +    nested-name-specifier [opt] type-name
> +    nested-name-specifier template simple-template-id
> +    computed-type-specifier
> +
>     Returns a TYPE indicating the class to be initialized for the first
> -   production (and the second in C++11).  Returns an IDENTIFIER_NODE
> -   indicating the data member to be initialized for the last production.  */
> +   production.  Returns an IDENTIFIER_NODE indicating the data member to
> +   be initialized for the second production.  */
>  
>  static tree
>  cp_parser_mem_initializer_id (cp_parser* parser)
> @@ -18015,10 +18097,16 @@ cp_parser_mem_initializer_id (cp_parser* parser)
>  			       /*class_head_p=*/false,
>  			       /*is_declaration=*/true);
>    /* If we found one, we're done.  */
> -  if (cp_parser_parse_definitely (parser))
> -    return id;
> -  /* Otherwise, look for an ordinary identifier.  */
> -  return cp_parser_identifier (parser);
> +  if (!cp_parser_parse_definitely (parser))
> +    /* Otherwise, look for an ordinary identifier.  */
> +    id = cp_parser_identifier (parser);
> +
> +  /* ": T...[N]" is a C++26 pack-index-specifier.  */
> +  if (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
> +      && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SQUARE))
> +    id = cp_parser_pack_index (parser, id);
> +
> +  return id;
>  }
>  
>  /* Overloading [gram.over] */
> @@ -20436,11 +20524,11 @@ cp_parser_type_specifier (cp_parser* parser,
>     C++11 Extension:
>  
>     simple-type-specifier:
> -     auto
> -     decltype ( expression )
>       char16_t
>       char32_t
>       __underlying_type ( type-id )
> +     computed-type-specifier
> +     placeholder-type-specifier
>  
>     C++17 extension:
>  
> @@ -20822,6 +20910,13 @@ cp_parser_simple_type_specifier (cp_parser* parser,
>  	    type = NULL_TREE;
>  	}
>  
> +      /* "T...[constant-expression]" is a C++26 pack-index-specifier.  */
> +      if (type
> +	  && type != error_mark_node
> +	  && cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
> +	  && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SQUARE))
> +	type = cp_parser_pack_index (parser, type);
> +
>        if (!type && flag_concepts && decl_specs)
>  	{
>  	  /* Try for a type-constraint with template arguments.  We check
> @@ -29103,12 +29198,21 @@ cp_parser_base_clause (cp_parser* parser)
>  /* Parse a base-specifier.
>  
>     base-specifier:
> -     attribute-specifier-seq [opt] :: [opt] nested-name-specifier [opt]
> -       class-name
> -     attribute-specifier-seq [opt] virtual access-specifier [opt] :: [opt]
> -       nested-name-specifier [opt] class-name
> -     attribute-specifier-seq [opt] access-specifier virtual [opt] :: [opt]
> -       nested-name-specifier [opt] class-name
> +     attribute-specifier-seq [opt] class-or-decltype
> +     attribute-specifier-seq [opt] virtual access-specifier [opt]
> +       class-or-decltype
> +     attribute-specifier-seq [opt] access-specifier virtual [opt]
> +       class-or-decltype
> +
> +   class-or-decltype:
> +     nested-name-specifier [opt] type-name
> +     nested-name-specifier template simple-template-id
> +     computed-type-specifier
> +
> +   access-specifier:
> +     private
> +     protected
> +     public
>  
>     Returns a TREE_LIST.  The TREE_PURPOSE will be one of
>     ACCESS_{DEFAULT,PUBLIC,PROTECTED,PRIVATE}_[VIRTUAL]_NODE to
> @@ -29237,6 +29341,10 @@ cp_parser_base_specifier (cp_parser* parser)
>  				   /*class_head_p=*/false,
>  				   /*is_declaration=*/true);
>        type = TREE_TYPE (type);
> +      /* ": T...[constant-expression]" is a C++26 pack-index-specifier.  */
> +      if (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
> +	  && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SQUARE))
> +	type = cp_parser_pack_index (parser, type);
>      }
>  
>    if (type == error_mark_node)
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index b590c32345f..e7f3ce38348 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -1786,7 +1786,8 @@ iterative_hash_template_arg (tree arg, hashval_t val)
>      case TYPE_PACK_EXPANSION:
>      case EXPR_PACK_EXPANSION:
>        val = iterative_hash_template_arg (PACK_EXPANSION_PATTERN (arg), val);
> -      return iterative_hash_template_arg (PACK_EXPANSION_EXTRA_ARGS (arg), val);
> +      val = iterative_hash_template_arg (PACK_EXPANSION_EXTRA_ARGS (arg), val);
> +      return iterative_hash_template_arg (PACK_EXPANSION_INDEX (arg), val);
>  
>      case TYPE_ARGUMENT_PACK:
>      case NONTYPE_ARGUMENT_PACK:
> @@ -3952,7 +3953,11 @@ find_parameter_packs_r (tree *tp, int *walk_subtrees, void* data)
>        break;
>  
>      case VAR_DECL:
> -      if (DECL_PACK_P (t))
> +      /* We can have
> +	   T...[0] a;
> +	   (T...[0])(a); // #1
> +	 where the 'a' in #1 is not a bare parameter pack.  */
> +      if (DECL_PACK_P (t) && !PACK_EXPANSION_INDEX (TREE_TYPE (t)))
>          {
>            /* We don't want to walk into the type of a variadic capture proxy,
>               because we don't want to see the type parameter pack.  */
> @@ -4027,6 +4032,10 @@ find_parameter_packs_r (tree *tp, int *walk_subtrees, void* data)
>  
>      case TYPE_PACK_EXPANSION:
>      case EXPR_PACK_EXPANSION:
> +      /* We can have an expansion of an expansion, such as "Ts...[Is]...",
> +	 so do look into the index.  */
> +      cp_walk_tree (&PACK_EXPANSION_INDEX (t), &find_parameter_packs_r, ppd,
> +		    ppd->visited);
>        *walk_subtrees = 0;
>        return NULL_TREE;
>  
> @@ -12524,11 +12533,19 @@ instantiate_class_template (tree type)
>  
>            if (PACK_EXPANSION_P (BINFO_TYPE (pbase_binfo)))
>              {
> -              expanded_bases = 
> +	      expanded_bases =
>  		tsubst_pack_expansion (BINFO_TYPE (pbase_binfo),
>  				       args, tf_error, NULL_TREE);
>                if (expanded_bases == error_mark_node)
>                  continue;
> +	      /* If there was a pack-index-specifier, we won't get
> +		 a TREE_VEC but the rest of the code assumes so.  */
> +	      if (PACK_EXPANSION_INDEX (BINFO_TYPE (pbase_binfo)))
> +		{
> +		  tree vec = make_tree_vec (1);
> +		  TREE_VEC_ELT (vec, 0) = expanded_bases;
> +		  expanded_bases = vec;
> +		}
>  
>                len = TREE_VEC_LENGTH (expanded_bases);
>              }
> @@ -13669,6 +13686,8 @@ tsubst_pack_expansion (tree t, tree args, tsubst_flags_t complain,
>  
>    levels = TMPL_ARGS_DEPTH (args);
>  
> +  tree index = tsubst_expr (PACK_EXPANSION_INDEX (t), args, complain, in_decl);
> +
>    /* Determine the argument packs that will instantiate the parameter
>       packs used in the expansion expression. While we're at it,
>       compute the number of arguments to be expanded and make sure it
> @@ -13814,6 +13833,10 @@ tsubst_pack_expansion (tree t, tree args, tsubst_flags_t complain,
>      {
>        tree args = ARGUMENT_PACK_ARGS (TREE_VALUE (packs));
>  
> +      /* C++26 Pack Indexing.  */
> +      if (index)
> +	return pack_index_element (index, args, complain);

I'd expect every pack index operator to hit this code path since its
pattern should always be a bare pack...

> +
>        /* If the argument pack is a single pack expansion, pull it out.  */
>        if (TREE_VEC_LENGTH (args) == 1
>  	  && pack_expansion_args_count (args))
> @@ -13946,6 +13969,10 @@ tsubst_pack_expansion (tree t, tree args, tsubst_flags_t complain,
>        && PACK_EXPANSION_P (TREE_VEC_ELT (result, 0)))
>      return TREE_VEC_ELT (result, 0);
>  
> +  /* C++26 Pack Indexing.  */
> +  if (index)
> +    return pack_index_element (index, result, complain);

... so this code path should be necessary?

> +
>    return result;
>  }
>  
> @@ -16897,17 +16924,23 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>  	    ctx = tsubst_pack_expansion (ctx, args,
>  					 complain | tf_qualifying_scope,
>  					 in_decl);
> -	    if (ctx == error_mark_node
> -		|| TREE_VEC_LENGTH (ctx) > 1)
> +	    if (ctx == error_mark_node)
>  	      return error_mark_node;
> -	    if (TREE_VEC_LENGTH (ctx) == 0)
> +	    /* If there was a pack-index-specifier, we won't get a TREE_VEC,
> +	       just the single element.  */
> +	    if (TREE_CODE (ctx) == TREE_VEC)
>  	      {
> -		if (complain & tf_error)
> -		  error ("%qD is instantiated for an empty pack",
> -			 TYPENAME_TYPE_FULLNAME (t));
> -		return error_mark_node;
> +		if (TREE_VEC_LENGTH (ctx) > 1)
> +		  return error_mark_node;
> +		if (TREE_VEC_LENGTH (ctx) == 0)
> +		  {
> +		    if (complain & tf_error)
> +		      error ("%qD is instantiated for an empty pack",
> +			     TYPENAME_TYPE_FULLNAME (t));
> +		    return error_mark_node;
> +		  }
> +		ctx = TREE_VEC_ELT (ctx, 0);
>  	      }
> -	    ctx = TREE_VEC_ELT (ctx, 0);
>  	  }
>  	else
>  	  ctx = tsubst_entering_scope (ctx, args,
> @@ -17041,13 +17074,20 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>  	else
>  	  {
>  	    bool id = DECLTYPE_TYPE_ID_EXPR_OR_MEMBER_ACCESS_P (t);
> -	    if (id && TREE_CODE (DECLTYPE_TYPE_EXPR (t)) == BIT_NOT_EXPR
> -		&& EXPR_P (type))
> +	    tree op = DECLTYPE_TYPE_EXPR (t);
> +	    if (id && TREE_CODE (op) == BIT_NOT_EXPR && EXPR_P (type))
>  	      /* In a template ~id could be either a complement expression
>  		 or an unqualified-id naming a destructor; if instantiating
>  		 it produces an expression, it's not an id-expression or
>  		 member access.  */
>  	      id = false;
> +	    /* With pack indexing, we don't know what it's going to expand to
> +	       until instantiation.  The intent is that a pack indexing
> +	       expression behaves exactly as the underlying expression
> +	       would.  */
> +	    else if (PACK_EXPANSION_P (op))
> +	      id = (!PACK_EXPANSION_PARENTHESIZED_P (op)
> +		    && unparenthesized_id_or_class_member_access_p (type));
>  	    type = finish_decltype_type (type, id, complain);
>  	  }
>  	return cp_build_qualified_type (type,
> @@ -17074,6 +17114,11 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>      case NONTYPE_ARGUMENT_PACK:
>        return tsubst_argument_pack (t, args, complain, in_decl);
>  
> +    case TYPE_PACK_EXPANSION:
> +      if (PACK_EXPANSION_INDEX (t))
> +	return tsubst_pack_expansion (t, args, complain, in_decl);
> +      gcc_fallthrough ();
> +
>      case VOID_CST:
>      case INTEGER_CST:
>      case REAL_CST:
> @@ -19596,6 +19641,8 @@ tsubst_stmt (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>        }
>  
>      case EXPR_PACK_EXPANSION:
> +      if (PACK_EXPANSION_INDEX (t))
> +	RETURN (tsubst_pack_expansion (t, args, complain, in_decl));
>        error ("invalid use of pack expansion expression");
>        RETURN (error_mark_node);
>  
> @@ -21776,6 +21823,8 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>        }
>  
>      case EXPR_PACK_EXPANSION:
> +      if (PACK_EXPANSION_INDEX (t))
> +	RETURN (tsubst_pack_expansion (t, args, complain, in_decl));
>        error ("invalid use of pack expansion expression");
>        RETURN (error_mark_node);
>  
> @@ -27685,6 +27734,14 @@ tsubst_initializer_list (tree t, tree argvec)
>                                                   NULL_TREE);
>            if (expanded_bases == error_mark_node)
>              continue;
> +	  /* If there was a pack-index-specifier, we won't get
> +	     a TREE_VEC but the rest of the code assumes so.  */
> +	  if (PACK_EXPANSION_INDEX (TREE_PURPOSE (t)))
> +	    {
> +	      tree vec = make_tree_vec (1);
> +	      TREE_VEC_ELT (vec, 0) = expanded_bases;
> +	      expanded_bases = vec;
> +	    }
>  
>            /* We'll be building separate TREE_LISTs of arguments for
>               each base.  */
> @@ -28115,7 +28172,7 @@ dependent_type_p_r (tree type)
>      }
>  
>    /* All TYPE_PACK_EXPANSIONs are dependent, because parameter packs must
> -     be template parameters.  */
> +     be template parameters.  This includes pack-index-specifiers.  */
>    if (TREE_CODE (type) == TYPE_PACK_EXPANSION)
>      return true;
>  
> @@ -28739,7 +28796,8 @@ type_dependent_expression_p (tree expression)
>        && uses_outer_template_parms_in_constraints (expression))
>      return true;
>  
> -  /* Always dependent, on the number of arguments if nothing else.  */
> +  /* Always dependent, on the number of arguments if nothing else.  This
> +     includes pack-index-expressions.  */
>    if (TREE_CODE (expression) == EXPR_PACK_EXPANSION)
>      return true;
>  
> @@ -31166,7 +31224,7 @@ do_class_deduction (tree ptype, tree tmpl, tree init, tree outer_targs,
>  
>  /* Return true if INIT is an unparenthesized id-expression or an
>     unparenthesized class member access.  Used for the argument of
> -   decltype(auto).  */
> +   decltype(auto), or for C++26 pack indexing.  */
>  
>  bool
>  unparenthesized_id_or_class_member_access_p (tree init)
> diff --git a/gcc/cp/ptree.cc b/gcc/cp/ptree.cc
> index 15e46752d01..82d08a31b22 100644
> --- a/gcc/cp/ptree.cc
> +++ b/gcc/cp/ptree.cc
> @@ -190,6 +190,7 @@ cxx_print_type (FILE *file, tree node, int indent)
>      case TYPE_PACK_EXPANSION:
>        print_node (file, "pattern", PACK_EXPANSION_PATTERN (node), indent + 4);
>        print_node (file, "args", PACK_EXPANSION_EXTRA_ARGS (node), indent + 4);
> +      print_node (file, "index", PACK_EXPANSION_INDEX (node), indent + 4);
>        return;
>  
>      default:
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index dabfb1ff53f..7ccd678252c 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -2452,6 +2452,8 @@ finish_parenthesized_expr (cp_expr expr)
>    tree stripped_expr = tree_strip_any_location_wrapper (expr);
>    if (TREE_CODE (stripped_expr) == STRING_CST)
>      PAREN_STRING_LITERAL_P (stripped_expr) = 1;
> +  else if (PACK_EXPANSION_P (stripped_expr))
> +    PACK_EXPANSION_PARENTHESIZED_P (stripped_expr) = true;
>  
>    expr = cp_expr (force_paren_expr (expr), expr.get_location ());
>  
> @@ -4181,7 +4183,9 @@ finish_base_specifier (tree base, tree access, bool virtual_p)
>        error ("invalid base-class specification");
>        result = NULL_TREE;
>      }
> -  else if (! MAYBE_CLASS_TYPE_P (base))
> +  else if (! MAYBE_CLASS_TYPE_P (base)
> +	   && ! (PACK_EXPANSION_P (base)
> +		 && PACK_EXPANSION_INDEX (base)))
>      {
>        error ("%qT is not a class type", base);
>        result = NULL_TREE;
> @@ -4837,34 +4841,66 @@ finish_underlying_type (tree type)
>    return underlying_type;
>  }
>  
> -/* Implement the __type_pack_element keyword: Return the type
> -   at index IDX within TYPES.  */
> +/* Return the type at index IDX within TYPES.  */
>  
>  static tree
> -finish_type_pack_element (tree idx, tree types, tsubst_flags_t complain)
> +get_vec_elt_checking (tree idx, tree types, bool pack_index_p,
> +		      tsubst_flags_t complain)
>  {
>    idx = maybe_constant_value (idx);
>    if (TREE_CODE (idx) != INTEGER_CST || !INTEGRAL_TYPE_P (TREE_TYPE (idx)))
>      {
>        if (complain & tf_error)
> -	error ("%<__type_pack_element%> index is not an integral constant");
> +	{
> +	  if (pack_index_p)
> +	    error ("pack index is not an integral constant");
> +	  else
> +	    error ("%<__type_pack_element%> index is not an integral constant");
> +	}
>        return error_mark_node;
>      }
>    if (tree_int_cst_sgn (idx) < 0)
>      {
>        if (complain & tf_error)
> -	error ("%<__type_pack_element%> index is negative");
> +	{
> +	  if (pack_index_p)
> +	    error ("pack index is negative");
> +	  else
> +	    error ("%<__type_pack_element%> index is negative");
> +	}
>        return error_mark_node;
>      }
>    if (wi::to_widest (idx) >= TREE_VEC_LENGTH (types))
>      {
>        if (complain & tf_error)
> -	error ("%<__type_pack_element%> index is out of range");
> +	{
> +	  if (pack_index_p)
> +	    error ("pack index is out of range");
> +	  else
> +	    error ("%<__type_pack_element%> index is out of range");
> +	}
>        return error_mark_node;
>      }
>    return TREE_VEC_ELT (types, tree_to_shwi (idx));
>  }
>  
> +/* Implement the __type_pack_element keyword: Return the type
> +   at index IDX within TYPES.  */
> +
> +static tree
> +finish_type_pack_element (tree idx, tree types, tsubst_flags_t complain)
> +{
> +  return get_vec_elt_checking (idx, types, /*pack_index_p=*/false, complain);
> +}
> +
> +/* In a pack-index T...[N], return the element at index IDX within TYPES.  */
> +
> +tree
> +pack_index_element (tree idx, tree types, tsubst_flags_t complain)
> +{
> +  return get_vec_elt_checking (idx, types, /*pack_index_p=*/true, complain);
> +}
> +
>  /* Implement the __direct_bases keyword: Return the direct base classes
>     of type.  */
>  
> diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> index c80ee068958..8637ab06d77 100644
> --- a/gcc/cp/tree.cc
> +++ b/gcc/cp/tree.cc
> @@ -1441,7 +1441,10 @@ cp_build_qualified_type (tree type, int type_quals,
>        tree t = PACK_EXPANSION_PATTERN (type);
>  
>        t = cp_build_qualified_type (t, type_quals, complain);
> -      return make_pack_expansion (t, complain);
> +      /* Regenerate the pack expansion with a qualified type.  */
> +      t = make_pack_expansion (t, complain);
> +      PACK_EXPANSION_INDEX (t) = PACK_EXPANSION_INDEX (type);
> +      return t;
>      }
>  
>    /* A reference or method type shall not be cv-qualified.
> @@ -4242,6 +4245,9 @@ cp_tree_equal (tree t1, tree t2)
>        if (!comp_template_args (PACK_EXPANSION_EXTRA_ARGS (t1),
>  			       PACK_EXPANSION_EXTRA_ARGS (t2)))
>  	return false;
> +      if (!cp_tree_equal (PACK_EXPANSION_INDEX (t1),
> +			  PACK_EXPANSION_INDEX (t2)))
> +	return false;
>        return true;
>  
>      case COMPONENT_REF:
> @@ -5575,12 +5581,16 @@ cp_walk_subtrees (tree *tp, int *walk_subtrees_p, walk_tree_fn func,
>      case TYPE_PACK_EXPANSION:
>        WALK_SUBTREE (TREE_TYPE (t));
>        WALK_SUBTREE (PACK_EXPANSION_EXTRA_ARGS (t));
> +      if (PACK_EXPANSION_INDEX (t))
> +	WALK_SUBTREE (PACK_EXPANSION_INDEX (t));
>        *walk_subtrees_p = 0;
>        break;
> -      
> +
>      case EXPR_PACK_EXPANSION:
>        WALK_SUBTREE (TREE_OPERAND (t, 0));
>        WALK_SUBTREE (PACK_EXPANSION_EXTRA_ARGS (t));
> +      if (PACK_EXPANSION_INDEX (t))
> +	WALK_SUBTREE (PACK_EXPANSION_INDEX (t));
>        *walk_subtrees_p = 0;
>        break;
>  
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index 71d879abef1..4f0d1bc8af2 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -1620,7 +1620,9 @@ structural_comptypes (tree t1, tree t2, int strict)
>        return (same_type_p (PACK_EXPANSION_PATTERN (t1),
>  			   PACK_EXPANSION_PATTERN (t2))
>  	      && comp_template_args (PACK_EXPANSION_EXTRA_ARGS (t1),
> -				     PACK_EXPANSION_EXTRA_ARGS (t2)));
> +				     PACK_EXPANSION_EXTRA_ARGS (t2))
> +	      && cp_tree_equal (PACK_EXPANSION_INDEX (t1),
> +				PACK_EXPANSION_INDEX (t2)));
>  
>      case DECLTYPE_TYPE:
>        if (DECLTYPE_TYPE_ID_EXPR_OR_MEMBER_ACCESS_P (t1)
> diff --git a/gcc/testsuite/g++.dg/cpp26/pack-indexing1.C b/gcc/testsuite/g++.dg/cpp26/pack-indexing1.C
> new file mode 100644
> index 00000000000..9d72c6582af
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp26/pack-indexing1.C
> @@ -0,0 +1,102 @@
> +// P2662R3 - Pack Indexing
> +// PR c++/113798
> +// { dg-do compile { target c++17 } }
> +// { dg-options "" }
> +
> +template<class, class> struct same_type;
> +template<class T> struct same_type<T, T> {};
> +
> +template<int I, typename... Ts>
> +using Type = Ts...[I]; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +
> +template<int I, auto... Ts>
> +constexpr auto Var = Ts...[I]; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +
> +template <int I, auto...Ts>
> +decltype(Ts...[I])  // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +foo ()
> +{
> +  return Ts...[I]; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +}
> +
> +template<typename... Ts>
> +struct S {
> +  Ts...[0] a; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +#if __cpp_concepts >= 201907L
> +  void foo (auto... Vs) {
> +    decltype(Vs...[1]) d1 = Vs...[1]; // { dg-warning "pack indexing only available with" "" { target { c++20 && c++23_down } } }
> +  }
> +#endif
> +};
> +
> +int
> +g ()
> +{
> +  using U = Type<1, char, int, float>;
> +  using U = int;
> +
> +  constexpr auto V = Var<2, 0, 1, 42>;
> +  static_assert (V == 42);
> +
> +  U r = foo<2, 0, 1, 42>();
> +
> +  return r;
> +}
> +
> +void
> +fn1 ()
> +{
> +  int i = 0;
> +  [&i](auto... pack) {
> +    // type is int
> +    decltype(pack...[0]) x5 = 42; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +    // type is int&
> +    [[maybe_unused]] decltype((pack...[0])) x6 = i; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +  }(0);
> +}
> +
> +#if __cpp_concepts >= 201907L
> +int
> +bar (auto... pack)
> +{
> +  (void) pack...[0]; // { dg-warning "pack indexing only available with" "" { target { c++20 && c++23_down } } }
> +  int x = pack...[0]; // { dg-warning "pack indexing only available with" "" { target { c++20 && c++23_down } } }
> +  return x;
> +}
> +#endif
> +
> +template<auto...pack>
> +void
> +fn2 ()
> +{
> +  [[maybe_unused]] decltype(pack...[0]) x1; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +  [[maybe_unused]] decltype((pack...[0])) x2; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +  same_type<decltype(x1), int>();
> +  same_type<decltype(x2), int>();
> +}
> +
> +template<typename... T>
> +void
> +fn3 (int p)
> +{
> +  T...[0] a = p; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +  (T...[0])(a);  // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +}
> +
> +template<int... Is>
> +void fn4 ()
> +{
> +  same_type<decltype(Is...[0]), int>();	// { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +  same_type<decltype((Is...[0])), int>(); // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +}
> +
> +void
> +g3 ()
> +{
> +  fn2<0>();
> +#if __cpp_concepts >= 201907L
> +  bar (0);
> +#endif
> +  S<int> s;
> +  fn4<0, 1, 2>();
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp26/pack-indexing2.C b/gcc/testsuite/g++.dg/cpp26/pack-indexing2.C
> new file mode 100644
> index 00000000000..c3c67d6ea34
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp26/pack-indexing2.C
> @@ -0,0 +1,111 @@
> +// P2662R3 - Pack Indexing
> +// PR c++/113798
> +// { dg-do compile { target c++26 } }
> +// Test invalid cases.
> +
> +template<int I, typename... Ts>
> +using Type = Typo...[I]; // { dg-error "does not name a type" }
> +
> +template<int I, auto... Ts>
> +constexpr auto Var = Typo...[I]; // { dg-error "no parameter packs" }
> +
> +template<typename... Ts>
> +void foo(Ts...[]);  // { dg-error "pack index missing" }
> +
> +template <typename... Ts>
> +void f(Ts...[1]);
> +
> +template<int... N>
> +int f2 (X... [N]); // { dg-error "contains no parameter packs" }
> +
> +struct X;
> +
> +template<typename T>
> +struct TX;
> +
> +template <typename T, auto V, template<typename> typename Tp>
> +void
> +bad (int i)
> +{
> +  i...[0];  // { dg-error "no parameter packs" }
> +  V...[0];  // { dg-error "no parameter packs" }
> +  X...[0] x;  // { dg-error "no parameter packs" }
> +  T...[0] t;  // { dg-error "no parameter packs" }
> +  Tp...[0] tp;  // { dg-error "expected" }
> +
> +  X...[0] xarr[1];  // { dg-error "no parameter packs" }
> +  T...[0] tarr[1];  // { dg-error "no parameter packs" }
> +  Tp...[0] tparr[1];  // { dg-error "expected" }
> +}
> +
> +template<int N>
> +int
> +getT (auto... Ts)
> +{
> +  return Ts...[N]; // { dg-error "pack index is out of range" }
> +}
> +
> +template<int N>
> +int
> +getT2 (auto... Ts)
> +{
> +  return Ts...[N]; // { dg-error "pack index is negative" }
> +}
> +
> +template<auto N, typename... Ts>
> +void
> +badtype ()
> +{
> +  Ts...[N] t; // { dg-error "pack index is out of range" }
> +}
> +
> +template<auto N, typename... Ts>
> +void
> +badtype2 ()
> +{
> +  Ts...[N] t; // { dg-error "pack index is negative" }
> +}
> +
> +int nonconst () { return 42; }
> +
> +template<typename... Ts>
> +void
> +badindex ()
> +{
> +  Ts...[nonconst ()] t; // { dg-error "pack index is not an integral constant" }
> +  // { dg-error "non-.constexpr. function" "" { target *-*-* } .-1 }
> +}
> +
> +template<typename... Ts>
> +struct broken {
> +  Ts...1;  // { dg-error "expected" }
> +  Ts...[;  // { dg-error "invalid" }
> +  Ts...[1;  // { dg-error "invalid" }
> +  Ts...[];  // { dg-error "pack index missing" }
> +
> +  void foo (auto...Vs) {
> +    decltype(Vs...[1]) d1 = Vs...[]; // { dg-error "pack index missing" }
> +    decltype(Vs...[1]) d2 = Vs...[; // { dg-error "expected" }
> +  }
> +};
> +
> +int main()
> +{
> +   // void f<int, double>(int [1], double [1])
> +  f<int, double>(nullptr, nullptr); // { dg-error "cannot convert" }
> +  bad<int, 0, TX>(42);
> +
> +  getT<0>(); // { dg-message "required from here" }
> +  getT<1>();  // { dg-message "required from here" }
> +  getT2<-1>();  // { dg-message "required from here" }
> +
> +  badtype<0>(); // { dg-message "required from here" }
> +  badtype<1, int>(); // { dg-message "required from here" }
> +  badtype2<-1>(); // { dg-message "required from here" }
> +  badtype2<-1, int>(); // { dg-message "required from here" }
> +
> +  badindex<int, int, int>();
> +
> +  bool b = nothere...[0]; // { dg-error "no parameter packs" }
> +  using E = nothere...[0]; // { dg-error "does not name a type" }
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp26/pack-indexing3.C b/gcc/testsuite/g++.dg/cpp26/pack-indexing3.C
> new file mode 100644
> index 00000000000..8c10b307f3a
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp26/pack-indexing3.C
> @@ -0,0 +1,41 @@
> +// P2662R3 - Pack Indexing
> +// PR c++/113798
> +// { dg-do compile { target c++26 } }
> +// From LLVM's cxx2c-pack-indexing.cpp.
> +
> +template<typename...>
> +struct X { };
> +
> +template<typename... T>
> +requires requires(T...[0]) { {T...[0](0)}; }
> +struct S : T...[1] {
> +  [[maybe_unused]] T...[1] base = {};
> +  using foo = T...[1];
> +  S() : T...[1]() { }
> +  X<T...[0]> x;
> +  const T...[0] f(T...[0]&& parm) noexcept((T...[0])0) {
> +    T...[0] (*test)(const volatile T...[0]**);
> +    thread_local T...[0] d;
> +    [[maybe_unused]] T...[0] a = parm;
> +    auto ptr = new T...[0](0);
> +    (*ptr).~T...[0]();
> +    return T...[0](0);
> +    typename T...[1]::foo b = 0;
> +    T...[1]::i = 0;
> +    return (T...[0])(a);
> +    new T...[0];
> +    [[maybe_unused]] auto l = []<T...[0]>(T...[0][1]) -> T...[0]{ return {}; };
> +    [[maybe_unused]] auto _ = l.template operator()<T...[0]{}>({0});
> +  }
> +  operator T...[0]() const { }
> +};
> +
> +struct base {
> +    using foo = int;
> +    static inline int i = 42;
> +};
> +
> +int main()
> +{
> +  S<int, base>().f(0);
> +}
> diff --git a/gcc/testsuite/g++.dg/cpp26/pack-indexing4.C b/gcc/testsuite/g++.dg/cpp26/pack-indexing4.C
> new file mode 100644
> index 00000000000..8decf3064bc
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp26/pack-indexing4.C
> @@ -0,0 +1,65 @@
> +// P2662R3 - Pack Indexing
> +// PR c++/113798
> +// { dg-do compile { target c++26 } }
> +// From LLVM's cxx2c-pack-indexing.cpp.
> +
> +template <class, class>
> +constexpr bool is_same = false;
> +template <class T>
> +constexpr bool is_same<T, T> = true;
> +
> +template <typename T>
> +constexpr bool
> +f (auto&&... p)
> +{
> +  return is_same<T, decltype(p...[0])>;
> +}
> +
> +void
> +g ()
> +{
> +    int a = 0;
> +    const int b = 0;
> +    static_assert(f<int&&>(0));
> +    static_assert(f<int&>(a));
> +    static_assert(f<const int&>(b));
> +}
> +
> +template<auto... p>
> +struct A {
> +  enum E {
> +    x = p...[0]
> +  };
> +};
> +static_assert(A<42>::x == 42);
> +
> +struct S { };
> +template<auto... p>
> +constexpr auto constant_initializer = p...[0];
> +constexpr auto InitOk = constant_initializer<S{}>;
> +
> +consteval int evaluate(auto... p) {
> +    return p...[0];
> +}
> +constexpr int x = evaluate(42, S{});
> +static_assert(x == 42);
> +
> +template <auto... Is>
> +struct IL{};
> +
> +template <typename... Ts>
> +struct TL{};
> +
> +template <typename Tl, typename Il>
> +struct SpliceImpl;
> +
> +template <typename... Ts, auto... Is>
> +struct SpliceImpl<TL<Ts...>, IL<Is...>>
> +{
> +    using type = TL<Ts...[Is]...>;
> +};
> +
> +template <typename Tl, typename Il>
> +using Splice = typename SpliceImpl<Tl, Il>::type;
> +using type = Splice<TL<char, short, long, double>, IL<1, 2>>;
> +static_assert(is_same<type, TL<short, long>>);
> diff --git a/gcc/testsuite/g++.dg/cpp26/pack-indexing5.C b/gcc/testsuite/g++.dg/cpp26/pack-indexing5.C
> new file mode 100644
> index 00000000000..901956e2dae
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp26/pack-indexing5.C
> @@ -0,0 +1,41 @@
> +// P2662R3 - Pack Indexing
> +// PR c++/113798
> +// { dg-do compile { target c++26 } }
> +
> +template<class, class> struct same_type;
> +template<class T> struct same_type<T, T> {};
> +
> +void
> +fn1 (auto... Ts)
> +{
> +  same_type<decltype(Ts...[0]), int>();
> +  same_type<decltype((Ts...[0])), int&>();
> +  same_type<decltype(Ts...[1]), unsigned int>();
> +  same_type<decltype((Ts...[1])), unsigned int&>();
> +}
> +
> +template<auto... Is>
> +void
> +fn2 ()
> +{
> +  same_type<decltype(Is...[0]), int>();
> +  same_type<decltype((Is...[0])), int>();
> +  same_type<decltype(Is...[1]), unsigned int>();
> +  same_type<decltype((Is...[1])), unsigned int>();
> +  same_type<decltype(Is...[2]), double>();
> +  same_type<decltype((Is...[2])), double>();
> +  same_type<decltype(Is...[3]), float>();
> +  same_type<decltype((Is...[3])), float>();
> +  same_type<decltype(Is...[4]), unsigned char>();
> +  same_type<decltype((Is...[4])), unsigned char>();
> +}
> +
> +static constexpr unsigned char c = 'A';
> +
> +void
> +g ()
> +{
> +  int i = 42;
> +  fn1 (i, 42u);
> +  fn2<0, 1u, 2.0, 3.f, c>();
> +}
> 
> base-commit: 5fd1c0c1b6968d55e3f997d67a4c149edf20c012
> -- 
> 2.46.2
> 
>
  
Jason Merrill Oct. 23, 2024, 3:04 p.m. UTC | #3
On 10/23/24 10:20 AM, Patrick Palka wrote:
> On Tue, 22 Oct 2024, Marek Polacek wrote:
> 
>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
>>
>> -- >8 --
>> This patch implements C++26 Pack Indexing, as described in
>> <https://wg21.link/P2662R3>.
>>
>> The issue discussing how to mangle pack indexes has not been resolved
>> yet <https://github.com/itanium-cxx-abi/cxx-abi/issues/175> and I've
>> made no attempt to address it so far.
>>
>> Rather than introducing a new template code for a pack indexing, I'm
>> adding a new operand to EXPR_PACK_EXPANSION to store the index; for
>> TYPE_PACK_EXPANSION, I'm stashing the index into TYPE_VALUES_RAW.  This
> 
> What are the pros and cons of reusing TYPE/EXPR_PACK_EXPANSION instead
> of creating two new tree codes for these operators (one of whose
> operands would itself be a bare TYPE/EXPR_PACK_EXPANSION)?
> 
> I feel a little iffy at first glance about reusing these tree codes
> since it muddles what "kind" of tree they are: currently they represent
> a _vector_ or types/exprs (which is reflected by their tcc_exceptional
> class), and with this approach they can now also represent a single
> type/expr (despite their tcc_exceptional class), depending on whether
> PACK_EXPANSION_INDEX is set.

Yeah, I made a similar comment.

> At the same time, the pattern of a generic *_PACK_EXPANSION can be
> anything whereas for these index operators we know it's always a single
> bare pack, so we also don't need the full expressivity of
> *_PACK_EXPANSION to represent these operators either.

I imagine that someone will want to extend it to indexing into an 
arbitrary pack expansion before long, so I wouldn't try too hard to 
simplify based on that assumption.

Jason
  
Patrick Palka Oct. 24, 2024, 8:29 p.m. UTC | #4
On Wed, 23 Oct 2024, Jason Merrill wrote:

> On 10/23/24 10:20 AM, Patrick Palka wrote:
> > On Tue, 22 Oct 2024, Marek Polacek wrote:
> > 
> > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > > 
> > > -- >8 --
> > > This patch implements C++26 Pack Indexing, as described in
> > > <https://wg21.link/P2662R3>.
> > > 
> > > The issue discussing how to mangle pack indexes has not been resolved
> > > yet <https://github.com/itanium-cxx-abi/cxx-abi/issues/175> and I've
> > > made no attempt to address it so far.
> > > 
> > > Rather than introducing a new template code for a pack indexing, I'm
> > > adding a new operand to EXPR_PACK_EXPANSION to store the index; for
> > > TYPE_PACK_EXPANSION, I'm stashing the index into TYPE_VALUES_RAW.  This
> > 
> > What are the pros and cons of reusing TYPE/EXPR_PACK_EXPANSION instead
> > of creating two new tree codes for these operators (one of whose
> > operands would itself be a bare TYPE/EXPR_PACK_EXPANSION)?
> > 
> > I feel a little iffy at first glance about reusing these tree codes
> > since it muddles what "kind" of tree they are: currently they represent
> > a _vector_ or types/exprs (which is reflected by their tcc_exceptional
> > class), and with this approach they can now also represent a single
> > type/expr (despite their tcc_exceptional class), depending on whether
> > PACK_EXPANSION_INDEX is set.

Oops, I just noticed that TYPE/EXPR_PACK_EXPANSION are tcc_type/expr
rather than tcc_exceptional, somewhat surprisingly.  I must've been
thinking about ARGUMENT_PACK_SELECT which is tcc_exceptional.  But I
guess conceptually they still represent a vector of types/exprs rather
than a single type/expr currently.

> 
> Yeah, I made a similar comment.

FWIW there's an interesting example mentioned in
https://github.com/itanium-cxx-abi/cxx-abi/issues/175:

    template <class... T> struct tuple {
      template <unsigned I> T...[I] get();
    };

    tuple<int, char, bool> t;

How should we represent the partial instantiation of T...[I] with
T={int, char, bool}?  IIUC with the current approach we could use
PACK_EXPANSION_EXTRA_ARGS to defer expansion until the index is known.

With new tree codes, e.g. PACK_INDEX_EXPR/TYPE, if we represent the
pack/pattern itself as a *_PACK_EXPANSION operand we could just
expand that immediately, yielding a TREE_VEC operand.  (And this
expansion shouldn't allocate due to the T... optimization in
tsubst_pack_expansion.)

> 
> > At the same time, the pattern of a generic *_PACK_EXPANSION can be
> > anything whereas for these index operators we know it's always a single
> > bare pack, so we also don't need the full expressivity of
> > *_PACK_EXPANSION to represent these operators either.
> 
> I imagine that someone will want to extend it to indexing into an arbitrary
> pack expansion before long, so I wouldn't try too hard to simplify based on
> that assumption.
> 
> Jason
> 
>
  
Jason Merrill Oct. 25, 2024, 5:09 p.m. UTC | #5
On 10/24/24 4:29 PM, Patrick Palka wrote:
> On Wed, 23 Oct 2024, Jason Merrill wrote:
> 
>> On 10/23/24 10:20 AM, Patrick Palka wrote:
>>> On Tue, 22 Oct 2024, Marek Polacek wrote:
>>>
>>>> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
>>>>
>>>> -- >8 --
>>>> This patch implements C++26 Pack Indexing, as described in
>>>> <https://wg21.link/P2662R3>.
>>>>
>>>> The issue discussing how to mangle pack indexes has not been resolved
>>>> yet <https://github.com/itanium-cxx-abi/cxx-abi/issues/175> and I've
>>>> made no attempt to address it so far.
>>>>
>>>> Rather than introducing a new template code for a pack indexing, I'm
>>>> adding a new operand to EXPR_PACK_EXPANSION to store the index; for
>>>> TYPE_PACK_EXPANSION, I'm stashing the index into TYPE_VALUES_RAW.  This
>>>
>>> What are the pros and cons of reusing TYPE/EXPR_PACK_EXPANSION instead
>>> of creating two new tree codes for these operators (one of whose
>>> operands would itself be a bare TYPE/EXPR_PACK_EXPANSION)?
>>>
>>> I feel a little iffy at first glance about reusing these tree codes
>>> since it muddles what "kind" of tree they are: currently they represent
>>> a _vector_ or types/exprs (which is reflected by their tcc_exceptional
>>> class), and with this approach they can now also represent a single
>>> type/expr (despite their tcc_exceptional class), depending on whether
>>> PACK_EXPANSION_INDEX is set.
> 
> Oops, I just noticed that TYPE/EXPR_PACK_EXPANSION are tcc_type/expr
> rather than tcc_exceptional, somewhat surprisingly.  I must've been
> thinking about ARGUMENT_PACK_SELECT which is tcc_exceptional.  But I
> guess conceptually they still represent a vector of types/exprs rather
> than a single type/expr currently.
> 
>>
>> Yeah, I made a similar comment.
> 
> FWIW there's an interesting example mentioned in
> https://github.com/itanium-cxx-abi/cxx-abi/issues/175:
> 
>      template <class... T> struct tuple {
>        template <unsigned I> T...[I] get();
>      };
> 
>      tuple<int, char, bool> t;
> 
> How should we represent the partial instantiation of T...[I] with
> T={int, char, bool}?  IIUC with the current approach we could use
> PACK_EXPANSION_EXTRA_ARGS to defer expansion until the index is known.

As I just commented there, I would expect mangling of T...[I] to just 
refer to the two template parameters, now that mangling properly 
distinguishes template parameters at different depths.  Using 
PACK_EXPANSION_EXTRA_ARGS would be consistent with that.

> With new tree codes, e.g. PACK_INDEX_EXPR/TYPE, if we represent the
> pack/pattern itself as a *_PACK_EXPANSION operand we could just
> expand that immediately, yielding a TREE_VEC operand.  (And this
> expansion shouldn't allocate due to the T... optimization in
> tsubst_pack_expansion.)
> 
>>
>>> At the same time, the pattern of a generic *_PACK_EXPANSION can be
>>> anything whereas for these index operators we know it's always a single
>>> bare pack, so we also don't need the full expressivity of
>>> *_PACK_EXPANSION to represent these operators either.
>>
>> I imagine that someone will want to extend it to indexing into an arbitrary
>> pack expansion before long, so I wouldn't try too hard to simplify based on
>> that assumption.
>>
>> Jason
>>
>>
>
  
Marek Polacek Oct. 29, 2024, 5:55 p.m. UTC | #6
On Wed, Oct 23, 2024 at 10:20:39AM -0400, Patrick Palka wrote:
> On Tue, 22 Oct 2024, Marek Polacek wrote:
> 
> > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > 
> > -- >8 --
> > This patch implements C++26 Pack Indexing, as described in
> > <https://wg21.link/P2662R3>.
> > 
> > The issue discussing how to mangle pack indexes has not been resolved
> > yet <https://github.com/itanium-cxx-abi/cxx-abi/issues/175> and I've
> > made no attempt to address it so far.
> > 
> > Rather than introducing a new template code for a pack indexing, I'm
> > adding a new operand to EXPR_PACK_EXPANSION to store the index; for
> > TYPE_PACK_EXPANSION, I'm stashing the index into TYPE_VALUES_RAW.  This
> 
> What are the pros and cons of reusing TYPE/EXPR_PACK_EXPANSION instead
> of creating two new tree codes for these operators (one of whose
> operands would itself be a bare TYPE/EXPR_PACK_EXPANSION)?
> 
> I feel a little iffy at first glance about reusing these tree codes
> since it muddles what "kind" of tree they are: currently they represent
> a _vector_ or types/exprs (which is reflected by their tcc_exceptional
> class), and with this approach they can now also represent a single
> type/expr (despite their tcc_exceptional class), depending on whether
> PACK_EXPANSION_INDEX is set.
> 
> At the same time, the pattern of a generic *_PACK_EXPANSION can be
> anything whereas for these index operators we know it's always a single
> bare pack, so we also don't need the full expressivity of
> *_PACK_EXPANSION to represent these operators either.
> 
> Maybe it's the case that reusing these tree codes significantly
> simplifies parts of the implementation?

That was pretty much the reason.  But now I think it's cleaner to introduce
new codes, and that the clarity is worth the pain of adding new codes.
 
> > @@ -13814,6 +13833,10 @@ tsubst_pack_expansion (tree t, tree args, tsubst_flags_t complain,
> >      {
> >        tree args = ARGUMENT_PACK_ARGS (TREE_VALUE (packs));
> >  
> > +      /* C++26 Pack Indexing.  */
> > +      if (index)
> > +	return pack_index_element (index, args, complain);
> 
> I'd expect every pack index operator to hit this code path since its
> pattern should always be a bare pack...
> 
> > +
> >        /* If the argument pack is a single pack expansion, pull it out.  */
> >        if (TREE_VEC_LENGTH (args) == 1
> >  	  && pack_expansion_args_count (args))
> > @@ -13946,6 +13969,10 @@ tsubst_pack_expansion (tree t, tree args, tsubst_flags_t complain,
> >        && PACK_EXPANSION_P (TREE_VEC_ELT (result, 0)))
> >      return TREE_VEC_ELT (result, 0);
> >  
> > +  /* C++26 Pack Indexing.  */
> > +  if (index)
> > +    return pack_index_element (index, result, complain);
> 
> ... so this code path should be necessary?

This is no longer in the v2 patch.

Marek
  
Marek Polacek Oct. 29, 2024, 5:58 p.m. UTC | #7
On Thu, Oct 24, 2024 at 04:29:02PM -0400, Patrick Palka wrote:
> On Wed, 23 Oct 2024, Jason Merrill wrote:
> 
> > On 10/23/24 10:20 AM, Patrick Palka wrote:
> > > On Tue, 22 Oct 2024, Marek Polacek wrote:
> > > 
> > > > Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> > > > 
> > > > -- >8 --
> > > > This patch implements C++26 Pack Indexing, as described in
> > > > <https://wg21.link/P2662R3>.
> > > > 
> > > > The issue discussing how to mangle pack indexes has not been resolved
> > > > yet <https://github.com/itanium-cxx-abi/cxx-abi/issues/175> and I've
> > > > made no attempt to address it so far.
> > > > 
> > > > Rather than introducing a new template code for a pack indexing, I'm
> > > > adding a new operand to EXPR_PACK_EXPANSION to store the index; for
> > > > TYPE_PACK_EXPANSION, I'm stashing the index into TYPE_VALUES_RAW.  This
> > > 
> > > What are the pros and cons of reusing TYPE/EXPR_PACK_EXPANSION instead
> > > of creating two new tree codes for these operators (one of whose
> > > operands would itself be a bare TYPE/EXPR_PACK_EXPANSION)?
> > > 
> > > I feel a little iffy at first glance about reusing these tree codes
> > > since it muddles what "kind" of tree they are: currently they represent
> > > a _vector_ or types/exprs (which is reflected by their tcc_exceptional
> > > class), and with this approach they can now also represent a single
> > > type/expr (despite their tcc_exceptional class), depending on whether
> > > PACK_EXPANSION_INDEX is set.
> 
> Oops, I just noticed that TYPE/EXPR_PACK_EXPANSION are tcc_type/expr
> rather than tcc_exceptional, somewhat surprisingly.  I must've been
> thinking about ARGUMENT_PACK_SELECT which is tcc_exceptional.  But I
> guess conceptually they still represent a vector of types/exprs rather
> than a single type/expr currently.
> 
> > 
> > Yeah, I made a similar comment.
> 
> FWIW there's an interesting example mentioned in
> https://github.com/itanium-cxx-abi/cxx-abi/issues/175:
> 
>     template <class... T> struct tuple {
>       template <unsigned I> T...[I] get();
>     };
> 
>     tuple<int, char, bool> t;

I've added that test in the v2 patch.
 
> How should we represent the partial instantiation of T...[I] with
> T={int, char, bool}?  IIUC with the current approach we could use
> PACK_EXPANSION_EXTRA_ARGS to defer expansion until the index is known.
> 
> With new tree codes, e.g. PACK_INDEX_EXPR/TYPE, if we represent the
> pack/pattern itself as a *_PACK_EXPANSION operand we could just
> expand that immediately, yielding a TREE_VEC operand.  (And this
> expansion shouldn't allocate due to the T... optimization in
> tsubst_pack_expansion.)
 
What happens now is that we see that the index is value-dep and so
tsubst_pack_index just returns the original pack index.  I'm not
sure I've done that correctly.

Thanks,
Marek
  

Patch

diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def
index 18f75108c7b..576765667d0 100644
--- a/gcc/cp/cp-tree.def
+++ b/gcc/cp/cp-tree.def
@@ -395,7 +395,7 @@  DEFTREECODE (TYPE_PACK_EXPANSION, "type_pack_expansion", tcc_type, 0)
 
    EXPR_PACK_EXPANSION plays precisely the same role as TYPE_PACK_EXPANSION,
    but will be used for expressions.  */
-DEFTREECODE (EXPR_PACK_EXPANSION, "expr_pack_expansion", tcc_expression, 3)
+DEFTREECODE (EXPR_PACK_EXPANSION, "expr_pack_expansion", tcc_expression, 4)
 
 /* Selects the Ith parameter out of an argument pack. This node will
    be used when instantiating pack expansions; see
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index a44100a2bc4..12472d95247 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -510,6 +510,7 @@  extern GTY(()) tree cp_global_trees[CPTI_MAX];
       OVL_LOOKUP_P (in OVERLOAD)
       LOOKUP_FOUND_P (in RECORD_TYPE, UNION_TYPE, ENUMERAL_TYPE, NAMESPACE_DECL)
       FNDECL_MANIFESTLY_CONST_EVALUATED (in FUNCTION_DECL)
+      PACK_EXPANSION_PARENTHESIZED_P (in *_PACK_EXPANSION)
    5: IDENTIFIER_VIRTUAL_P (in IDENTIFIER_NODE)
       FUNCTION_RVALUE_QUALIFIED (in FUNCTION_TYPE, METHOD_TYPE)
       CALL_EXPR_REVERSE_ARGS (in CALL_EXPR, AGGR_INIT_EXPR)
@@ -4025,6 +4026,12 @@  struct GTY(()) lang_decl {
     ? &TYPE_MAX_VALUE_RAW (NODE)			\
     : &TREE_OPERAND ((NODE), 2))
 
+/* For a pack-index T...[N], the index N.  */
+#define PACK_EXPANSION_INDEX(NODE) \
+  *(TREE_CODE (PACK_EXPANSION_CHECK (NODE)) == TYPE_PACK_EXPANSION \
+    ? &TYPE_VALUES_RAW (NODE)			\
+    : &TREE_OPERAND ((NODE), 3))
+
 /* True iff this pack expansion is within a function context.  */
 #define PACK_EXPANSION_LOCAL_P(NODE) \
   TREE_LANG_FLAG_0 (PACK_EXPANSION_CHECK (NODE))
@@ -4042,6 +4049,11 @@  struct GTY(()) lang_decl {
 #define PACK_EXPANSION_FORCE_EXTRA_ARGS_P(NODE) \
   TREE_LANG_FLAG_3 (PACK_EXPANSION_CHECK (NODE))
 
+/* Indicates whether a pack expansion has been parenthesized.  Used for
+   a pack expansion in a decltype.  */
+#define PACK_EXPANSION_PARENTHESIZED_P(NODE) \
+  TREE_LANG_FLAG_4 (PACK_EXPANSION_CHECK (NODE))
+
 /* True iff the wildcard can match a template parameter pack.  */
 #define WILDCARD_PACK_P(NODE) TREE_LANG_FLAG_0 (NODE)
 
@@ -7906,6 +7918,7 @@  extern tree finish_underlying_type	        (tree);
 extern tree calculate_bases                     (tree, tsubst_flags_t);
 extern tree finish_bases                        (tree, bool);
 extern tree calculate_direct_bases              (tree, tsubst_flags_t);
+extern tree pack_index_element			(tree, tree, tsubst_flags_t);
 extern tree finish_offsetof			(tree, tree, location_t);
 extern void finish_decl_cleanup			(tree, tree);
 extern void finish_eh_cleanup			(tree);
diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
index 41e6bdfdda5..47111195f72 100644
--- a/gcc/cp/cxx-pretty-print.cc
+++ b/gcc/cp/cxx-pretty-print.cc
@@ -1216,6 +1216,12 @@  cxx_pretty_printer::expression (tree t)
     case EXPR_PACK_EXPANSION:
       expression (PACK_EXPANSION_PATTERN (t));
       pp_cxx_ws_string (this, "...");
+      if (PACK_EXPANSION_INDEX (t))
+	{
+	  pp_cxx_left_bracket (this);
+	  expression (PACK_EXPANSION_INDEX (t));
+	  pp_cxx_right_bracket (this);
+	}
       break;
 
     case UNARY_LEFT_FOLD_EXPR:
@@ -1916,6 +1922,12 @@  cxx_pretty_printer::type_id (tree t)
     case TYPE_PACK_EXPANSION:
       type_id (PACK_EXPANSION_PATTERN (t));
       pp_cxx_ws_string (this, "...");
+      if (PACK_EXPANSION_INDEX (t))
+	{
+	  pp_cxx_left_bracket (this);
+	  expression (PACK_EXPANSION_INDEX (t));
+	  pp_cxx_right_bracket (this);
+	}
       break;
 
     case TYPE_ARGUMENT_PACK:
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 7281818be8f..311f4ad6cd1 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -17040,8 +17040,12 @@  xref_basetypes (tree ref, tree base_list)
 	}
 
       if (PACK_EXPANSION_P (TREE_VALUE (base_list)))
-        /* Regenerate the pack expansion for the bases. */
-        basetype = make_pack_expansion (basetype);
+	{
+	  /* Regenerate the pack expansion for the bases. */
+	  basetype = make_pack_expansion (basetype);
+	  PACK_EXPANSION_INDEX (basetype)
+	    = PACK_EXPANSION_INDEX (TREE_VALUE (base_list));
+	}
 
       TYPE_MARKED_P (basetype) = 1;
 
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 65f70c595cf..33f964a11bd 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -818,6 +818,12 @@  dump_type (cxx_pretty_printer *pp, tree t, int flags)
     case TYPE_PACK_EXPANSION:
       dump_type (pp, PACK_EXPANSION_PATTERN (t), flags);
       pp_cxx_ws_string (pp, "...");
+      if (PACK_EXPANSION_INDEX (t))
+	{
+	  pp_cxx_left_bracket (pp);
+	  dump_expr (pp, PACK_EXPANSION_INDEX (t), flags & ~TFF_EXPR_IN_PARENS);
+	  pp_cxx_right_bracket (pp);
+	}
       break;
 
     case TYPE_ARGUMENT_PACK:
diff --git a/gcc/cp/mangle.cc b/gcc/cp/mangle.cc
index 17988d69e1e..8c8a89897c9 100644
--- a/gcc/cp/mangle.cc
+++ b/gcc/cp/mangle.cc
@@ -2605,6 +2605,8 @@  write_type (tree type)
             case TYPE_PACK_EXPANSION:
               write_string ("Dp");
               write_type (PACK_EXPANSION_PATTERN (type));
+	      /* TODO: Mangle PACK_EXPANSION_INDEX
+		 <https://github.com/itanium-cxx-abi/cxx-abi/issues/175>  */
               break;
 
             case DECLTYPE_TYPE:
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index fd9b1d3bf2e..da3e2b71a66 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -9207,6 +9207,7 @@  trees_out::type_node (tree type)
 	u (PACK_EXPANSION_LOCAL_P (type));
       tree_node (PACK_EXPANSION_PARAMETER_PACKS (type));
       tree_node (PACK_EXPANSION_EXTRA_ARGS (type));
+      tree_node (PACK_EXPANSION_INDEX (type));
       break;
 
     case TYPENAME_TYPE:
@@ -9763,6 +9764,7 @@  trees_in::tree_node (bool is_use)
 	      bool local = u ();
 	      tree param_packs = tree_node ();
 	      tree extra_args = tree_node ();
+	      tree index = tree_node ();
 	      if (!get_overrun ())
 		{
 		  tree expn = cxx_make_type (TYPE_PACK_EXPANSION);
@@ -9771,6 +9773,7 @@  trees_in::tree_node (bool is_use)
 		  PACK_EXPANSION_PARAMETER_PACKS (expn) = param_packs;
 		  PACK_EXPANSION_EXTRA_ARGS (expn) = extra_args;
 		  PACK_EXPANSION_LOCAL_P (expn) = local;
+		  PACK_EXPANSION_INDEX (expn) = index;
 		  res = expn;
 		}
 	    }
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 9d31a975dcf..7a054fb6c05 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -5739,6 +5739,56 @@  cp_parser_fold_expression (cp_parser *parser, tree expr1)
   return finish_binary_fold_expr (loc, expr1, expr2, op);
 }
 
+/*  Parse a pack-index-specifier:
+
+    pack-index-specifier:
+      typedef-name ... [ constant-expression ]
+
+    or a pack-index-expression:
+
+    pack-index-expression:
+      id-expression ... [ constant-expression ]
+
+    PACK is the parsed typedef-name or the id-expression.  Returns
+    either an EXPR_PACK_EXPANSION or TYPE_PACK_EXPANSION.  */
+
+static tree
+cp_parser_pack_index (cp_parser *parser, tree pack)
+{
+  if (cxx_dialect < cxx26)
+    pedwarn (cp_lexer_peek_token (parser->lexer)->location,
+	     OPT_Wc__26_extensions, "pack indexing only available with "
+	     "%<-std=c++2c%> or %<-std=gnu++2c%>");
+  /* Consume the '...' token.  */
+  cp_lexer_consume_token (parser->lexer);
+  /* Consume the '['.  */
+  cp_lexer_consume_token (parser->lexer);
+
+  if (cp_lexer_next_token_is (parser->lexer, CPP_CLOSE_SQUARE))
+    {
+      error_at (cp_lexer_peek_token (parser->lexer)->location,
+		"pack index missing");
+      cp_lexer_consume_token (parser->lexer);
+      return error_mark_node;
+    }
+
+  tree expr = cp_parser_constant_expression (parser,
+					     /*allow_non_constant_p=*/false,
+					     /*non_constant_p=*/nullptr,
+					     /*strict_p=*/true);
+  /* Consume the ']'.  */
+  cp_parser_require (parser, CPP_CLOSE_SQUARE, RT_CLOSE_SQUARE);
+
+  if (TREE_CODE (pack) == TYPE_DECL)
+    pack = TREE_TYPE (pack);
+  pack = make_pack_expansion (pack);
+  if (pack == error_mark_node)
+    return error_mark_node;
+  PACK_EXPANSION_INDEX (pack) = expr;
+
+  return pack;
+}
+
 /* Parse a primary-expression.
 
    primary-expression:
@@ -6368,6 +6418,12 @@  cp_parser_primary_expression (cp_parser *parser,
 	  = make_location (caret_loc, start_loc, finish_loc);
 
 	decl.set_location (combined_loc);
+
+	/* "T...[constant-expression]" is a C++26 pack-index-expression.  */
+	if (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
+	    && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SQUARE))
+	  decl = cp_parser_pack_index (parser, decl);
+
 	return decl;
       }
 
@@ -6411,6 +6467,7 @@  missing_template_diag (location_t loc, diagnostic_t diag_kind = DK_WARNING)
    id-expression:
      unqualified-id
      qualified-id
+     pack-index-expression
 
    qualified-id:
      :: [opt] nested-name-specifier template [opt] unqualified-id
@@ -6593,7 +6650,9 @@  cp_parser_id_expression (cp_parser *parser,
      identifier
      operator-function-id
      conversion-function-id
-     ~ class-name
+     literal-operator-id
+     ~ type-name
+     ~ computed-type-specifier
      template-id
 
    If TEMPLATE_KEYWORD_P is TRUE, we have just seen the `template'
@@ -6900,6 +6959,14 @@  cp_parser_unqualified_id (cp_parser* parser,
 		    "typedef-name %qD used as destructor declarator",
 		    type_decl);
 
+	/* "~T...[N]" is a C++26 pack-index-specifier.  */
+	if (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
+	    && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SQUARE))
+	  {
+	    type_decl = cp_parser_pack_index (parser, type_decl);
+	    return build_min_nt_loc (loc, BIT_NOT_EXPR, type_decl);
+	  }
+
 	return build_min_nt_loc (loc, BIT_NOT_EXPR, TREE_TYPE (type_decl));
       }
 
@@ -6970,9 +7037,10 @@  check_template_keyword_in_nested_name_spec (tree name)
      class-or-namespace-name :: nested-name-specifier [opt]
      class-or-namespace-name :: template nested-name-specifier [opt]
 
-   nested-name-specifier: [C++0x]
+   nested-name-specifier: [C++11]
      type-name ::
      namespace-name ::
+     computed-type-specifier ::
      nested-name-specifier identifier ::
      nested-name-specifier template [opt] simple-template-id ::
 
@@ -7080,6 +7148,10 @@  cp_parser_nested_name_specifier_opt (cp_parser *parser,
 	    }
 
 	  if (token->type != CPP_SCOPE
+	      /* See if a pack-index-specifier follows.  */
+	      && !(token->type == CPP_ELLIPSIS
+		   && cp_lexer_peek_nth_token (parser->lexer, 3)->type
+		      == CPP_OPEN_SQUARE)
 	      && !cp_parser_nth_token_starts_template_argument_list_p
 		  (parser, 2))
 	    break;
@@ -7127,6 +7199,12 @@  cp_parser_nested_name_specifier_opt (cp_parser *parser,
                                        check_dependency_p,
                                        type_p,
                                        is_declaration);
+
+      /* "T...[constant-expression]" is a C++26 pack-index-specifier.  */
+      if (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
+	  && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SQUARE))
+      new_scope = cp_parser_pack_index (parser, new_scope);
+
       /* Look for the `::' token.  */
       cp_parser_require (parser, CPP_SCOPE, RT_SCOPE);
 
@@ -17938,13 +18016,17 @@  cp_parser_mem_initializer (cp_parser* parser)
 /* Parse a mem-initializer-id.
 
    mem-initializer-id:
-     :: [opt] nested-name-specifier [opt] class-name
-     decltype-specifier (C++11)
+     class-or-decltype
      identifier
 
+  class-or-decltype:
+    nested-name-specifier [opt] type-name
+    nested-name-specifier template simple-template-id
+    computed-type-specifier
+
    Returns a TYPE indicating the class to be initialized for the first
-   production (and the second in C++11).  Returns an IDENTIFIER_NODE
-   indicating the data member to be initialized for the last production.  */
+   production.  Returns an IDENTIFIER_NODE indicating the data member to
+   be initialized for the second production.  */
 
 static tree
 cp_parser_mem_initializer_id (cp_parser* parser)
@@ -18015,10 +18097,16 @@  cp_parser_mem_initializer_id (cp_parser* parser)
 			       /*class_head_p=*/false,
 			       /*is_declaration=*/true);
   /* If we found one, we're done.  */
-  if (cp_parser_parse_definitely (parser))
-    return id;
-  /* Otherwise, look for an ordinary identifier.  */
-  return cp_parser_identifier (parser);
+  if (!cp_parser_parse_definitely (parser))
+    /* Otherwise, look for an ordinary identifier.  */
+    id = cp_parser_identifier (parser);
+
+  /* ": T...[N]" is a C++26 pack-index-specifier.  */
+  if (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
+      && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SQUARE))
+    id = cp_parser_pack_index (parser, id);
+
+  return id;
 }
 
 /* Overloading [gram.over] */
@@ -20436,11 +20524,11 @@  cp_parser_type_specifier (cp_parser* parser,
    C++11 Extension:
 
    simple-type-specifier:
-     auto
-     decltype ( expression )
      char16_t
      char32_t
      __underlying_type ( type-id )
+     computed-type-specifier
+     placeholder-type-specifier
 
    C++17 extension:
 
@@ -20822,6 +20910,13 @@  cp_parser_simple_type_specifier (cp_parser* parser,
 	    type = NULL_TREE;
 	}
 
+      /* "T...[constant-expression]" is a C++26 pack-index-specifier.  */
+      if (type
+	  && type != error_mark_node
+	  && cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
+	  && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SQUARE))
+	type = cp_parser_pack_index (parser, type);
+
       if (!type && flag_concepts && decl_specs)
 	{
 	  /* Try for a type-constraint with template arguments.  We check
@@ -29103,12 +29198,21 @@  cp_parser_base_clause (cp_parser* parser)
 /* Parse a base-specifier.
 
    base-specifier:
-     attribute-specifier-seq [opt] :: [opt] nested-name-specifier [opt]
-       class-name
-     attribute-specifier-seq [opt] virtual access-specifier [opt] :: [opt]
-       nested-name-specifier [opt] class-name
-     attribute-specifier-seq [opt] access-specifier virtual [opt] :: [opt]
-       nested-name-specifier [opt] class-name
+     attribute-specifier-seq [opt] class-or-decltype
+     attribute-specifier-seq [opt] virtual access-specifier [opt]
+       class-or-decltype
+     attribute-specifier-seq [opt] access-specifier virtual [opt]
+       class-or-decltype
+
+   class-or-decltype:
+     nested-name-specifier [opt] type-name
+     nested-name-specifier template simple-template-id
+     computed-type-specifier
+
+   access-specifier:
+     private
+     protected
+     public
 
    Returns a TREE_LIST.  The TREE_PURPOSE will be one of
    ACCESS_{DEFAULT,PUBLIC,PROTECTED,PRIVATE}_[VIRTUAL]_NODE to
@@ -29237,6 +29341,10 @@  cp_parser_base_specifier (cp_parser* parser)
 				   /*class_head_p=*/false,
 				   /*is_declaration=*/true);
       type = TREE_TYPE (type);
+      /* ": T...[constant-expression]" is a C++26 pack-index-specifier.  */
+      if (cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
+	  && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SQUARE))
+	type = cp_parser_pack_index (parser, type);
     }
 
   if (type == error_mark_node)
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index b590c32345f..e7f3ce38348 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -1786,7 +1786,8 @@  iterative_hash_template_arg (tree arg, hashval_t val)
     case TYPE_PACK_EXPANSION:
     case EXPR_PACK_EXPANSION:
       val = iterative_hash_template_arg (PACK_EXPANSION_PATTERN (arg), val);
-      return iterative_hash_template_arg (PACK_EXPANSION_EXTRA_ARGS (arg), val);
+      val = iterative_hash_template_arg (PACK_EXPANSION_EXTRA_ARGS (arg), val);
+      return iterative_hash_template_arg (PACK_EXPANSION_INDEX (arg), val);
 
     case TYPE_ARGUMENT_PACK:
     case NONTYPE_ARGUMENT_PACK:
@@ -3952,7 +3953,11 @@  find_parameter_packs_r (tree *tp, int *walk_subtrees, void* data)
       break;
 
     case VAR_DECL:
-      if (DECL_PACK_P (t))
+      /* We can have
+	   T...[0] a;
+	   (T...[0])(a); // #1
+	 where the 'a' in #1 is not a bare parameter pack.  */
+      if (DECL_PACK_P (t) && !PACK_EXPANSION_INDEX (TREE_TYPE (t)))
         {
           /* We don't want to walk into the type of a variadic capture proxy,
              because we don't want to see the type parameter pack.  */
@@ -4027,6 +4032,10 @@  find_parameter_packs_r (tree *tp, int *walk_subtrees, void* data)
 
     case TYPE_PACK_EXPANSION:
     case EXPR_PACK_EXPANSION:
+      /* We can have an expansion of an expansion, such as "Ts...[Is]...",
+	 so do look into the index.  */
+      cp_walk_tree (&PACK_EXPANSION_INDEX (t), &find_parameter_packs_r, ppd,
+		    ppd->visited);
       *walk_subtrees = 0;
       return NULL_TREE;
 
@@ -12524,11 +12533,19 @@  instantiate_class_template (tree type)
 
           if (PACK_EXPANSION_P (BINFO_TYPE (pbase_binfo)))
             {
-              expanded_bases = 
+	      expanded_bases =
 		tsubst_pack_expansion (BINFO_TYPE (pbase_binfo),
 				       args, tf_error, NULL_TREE);
               if (expanded_bases == error_mark_node)
                 continue;
+	      /* If there was a pack-index-specifier, we won't get
+		 a TREE_VEC but the rest of the code assumes so.  */
+	      if (PACK_EXPANSION_INDEX (BINFO_TYPE (pbase_binfo)))
+		{
+		  tree vec = make_tree_vec (1);
+		  TREE_VEC_ELT (vec, 0) = expanded_bases;
+		  expanded_bases = vec;
+		}
 
               len = TREE_VEC_LENGTH (expanded_bases);
             }
@@ -13669,6 +13686,8 @@  tsubst_pack_expansion (tree t, tree args, tsubst_flags_t complain,
 
   levels = TMPL_ARGS_DEPTH (args);
 
+  tree index = tsubst_expr (PACK_EXPANSION_INDEX (t), args, complain, in_decl);
+
   /* Determine the argument packs that will instantiate the parameter
      packs used in the expansion expression. While we're at it,
      compute the number of arguments to be expanded and make sure it
@@ -13814,6 +13833,10 @@  tsubst_pack_expansion (tree t, tree args, tsubst_flags_t complain,
     {
       tree args = ARGUMENT_PACK_ARGS (TREE_VALUE (packs));
 
+      /* C++26 Pack Indexing.  */
+      if (index)
+	return pack_index_element (index, args, complain);
+
       /* If the argument pack is a single pack expansion, pull it out.  */
       if (TREE_VEC_LENGTH (args) == 1
 	  && pack_expansion_args_count (args))
@@ -13946,6 +13969,10 @@  tsubst_pack_expansion (tree t, tree args, tsubst_flags_t complain,
       && PACK_EXPANSION_P (TREE_VEC_ELT (result, 0)))
     return TREE_VEC_ELT (result, 0);
 
+  /* C++26 Pack Indexing.  */
+  if (index)
+    return pack_index_element (index, result, complain);
+
   return result;
 }
 
@@ -16897,17 +16924,23 @@  tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 	    ctx = tsubst_pack_expansion (ctx, args,
 					 complain | tf_qualifying_scope,
 					 in_decl);
-	    if (ctx == error_mark_node
-		|| TREE_VEC_LENGTH (ctx) > 1)
+	    if (ctx == error_mark_node)
 	      return error_mark_node;
-	    if (TREE_VEC_LENGTH (ctx) == 0)
+	    /* If there was a pack-index-specifier, we won't get a TREE_VEC,
+	       just the single element.  */
+	    if (TREE_CODE (ctx) == TREE_VEC)
 	      {
-		if (complain & tf_error)
-		  error ("%qD is instantiated for an empty pack",
-			 TYPENAME_TYPE_FULLNAME (t));
-		return error_mark_node;
+		if (TREE_VEC_LENGTH (ctx) > 1)
+		  return error_mark_node;
+		if (TREE_VEC_LENGTH (ctx) == 0)
+		  {
+		    if (complain & tf_error)
+		      error ("%qD is instantiated for an empty pack",
+			     TYPENAME_TYPE_FULLNAME (t));
+		    return error_mark_node;
+		  }
+		ctx = TREE_VEC_ELT (ctx, 0);
 	      }
-	    ctx = TREE_VEC_ELT (ctx, 0);
 	  }
 	else
 	  ctx = tsubst_entering_scope (ctx, args,
@@ -17041,13 +17074,20 @@  tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 	else
 	  {
 	    bool id = DECLTYPE_TYPE_ID_EXPR_OR_MEMBER_ACCESS_P (t);
-	    if (id && TREE_CODE (DECLTYPE_TYPE_EXPR (t)) == BIT_NOT_EXPR
-		&& EXPR_P (type))
+	    tree op = DECLTYPE_TYPE_EXPR (t);
+	    if (id && TREE_CODE (op) == BIT_NOT_EXPR && EXPR_P (type))
 	      /* In a template ~id could be either a complement expression
 		 or an unqualified-id naming a destructor; if instantiating
 		 it produces an expression, it's not an id-expression or
 		 member access.  */
 	      id = false;
+	    /* With pack indexing, we don't know what it's going to expand to
+	       until instantiation.  The intent is that a pack indexing
+	       expression behaves exactly as the underlying expression
+	       would.  */
+	    else if (PACK_EXPANSION_P (op))
+	      id = (!PACK_EXPANSION_PARENTHESIZED_P (op)
+		    && unparenthesized_id_or_class_member_access_p (type));
 	    type = finish_decltype_type (type, id, complain);
 	  }
 	return cp_build_qualified_type (type,
@@ -17074,6 +17114,11 @@  tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
     case NONTYPE_ARGUMENT_PACK:
       return tsubst_argument_pack (t, args, complain, in_decl);
 
+    case TYPE_PACK_EXPANSION:
+      if (PACK_EXPANSION_INDEX (t))
+	return tsubst_pack_expansion (t, args, complain, in_decl);
+      gcc_fallthrough ();
+
     case VOID_CST:
     case INTEGER_CST:
     case REAL_CST:
@@ -19596,6 +19641,8 @@  tsubst_stmt (tree t, tree args, tsubst_flags_t complain, tree in_decl)
       }
 
     case EXPR_PACK_EXPANSION:
+      if (PACK_EXPANSION_INDEX (t))
+	RETURN (tsubst_pack_expansion (t, args, complain, in_decl));
       error ("invalid use of pack expansion expression");
       RETURN (error_mark_node);
 
@@ -21776,6 +21823,8 @@  tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
       }
 
     case EXPR_PACK_EXPANSION:
+      if (PACK_EXPANSION_INDEX (t))
+	RETURN (tsubst_pack_expansion (t, args, complain, in_decl));
       error ("invalid use of pack expansion expression");
       RETURN (error_mark_node);
 
@@ -27685,6 +27734,14 @@  tsubst_initializer_list (tree t, tree argvec)
                                                  NULL_TREE);
           if (expanded_bases == error_mark_node)
             continue;
+	  /* If there was a pack-index-specifier, we won't get
+	     a TREE_VEC but the rest of the code assumes so.  */
+	  if (PACK_EXPANSION_INDEX (TREE_PURPOSE (t)))
+	    {
+	      tree vec = make_tree_vec (1);
+	      TREE_VEC_ELT (vec, 0) = expanded_bases;
+	      expanded_bases = vec;
+	    }
 
           /* We'll be building separate TREE_LISTs of arguments for
              each base.  */
@@ -28115,7 +28172,7 @@  dependent_type_p_r (tree type)
     }
 
   /* All TYPE_PACK_EXPANSIONs are dependent, because parameter packs must
-     be template parameters.  */
+     be template parameters.  This includes pack-index-specifiers.  */
   if (TREE_CODE (type) == TYPE_PACK_EXPANSION)
     return true;
 
@@ -28739,7 +28796,8 @@  type_dependent_expression_p (tree expression)
       && uses_outer_template_parms_in_constraints (expression))
     return true;
 
-  /* Always dependent, on the number of arguments if nothing else.  */
+  /* Always dependent, on the number of arguments if nothing else.  This
+     includes pack-index-expressions.  */
   if (TREE_CODE (expression) == EXPR_PACK_EXPANSION)
     return true;
 
@@ -31166,7 +31224,7 @@  do_class_deduction (tree ptype, tree tmpl, tree init, tree outer_targs,
 
 /* Return true if INIT is an unparenthesized id-expression or an
    unparenthesized class member access.  Used for the argument of
-   decltype(auto).  */
+   decltype(auto), or for C++26 pack indexing.  */
 
 bool
 unparenthesized_id_or_class_member_access_p (tree init)
diff --git a/gcc/cp/ptree.cc b/gcc/cp/ptree.cc
index 15e46752d01..82d08a31b22 100644
--- a/gcc/cp/ptree.cc
+++ b/gcc/cp/ptree.cc
@@ -190,6 +190,7 @@  cxx_print_type (FILE *file, tree node, int indent)
     case TYPE_PACK_EXPANSION:
       print_node (file, "pattern", PACK_EXPANSION_PATTERN (node), indent + 4);
       print_node (file, "args", PACK_EXPANSION_EXTRA_ARGS (node), indent + 4);
+      print_node (file, "index", PACK_EXPANSION_INDEX (node), indent + 4);
       return;
 
     default:
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index dabfb1ff53f..7ccd678252c 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -2452,6 +2452,8 @@  finish_parenthesized_expr (cp_expr expr)
   tree stripped_expr = tree_strip_any_location_wrapper (expr);
   if (TREE_CODE (stripped_expr) == STRING_CST)
     PAREN_STRING_LITERAL_P (stripped_expr) = 1;
+  else if (PACK_EXPANSION_P (stripped_expr))
+    PACK_EXPANSION_PARENTHESIZED_P (stripped_expr) = true;
 
   expr = cp_expr (force_paren_expr (expr), expr.get_location ());
 
@@ -4181,7 +4183,9 @@  finish_base_specifier (tree base, tree access, bool virtual_p)
       error ("invalid base-class specification");
       result = NULL_TREE;
     }
-  else if (! MAYBE_CLASS_TYPE_P (base))
+  else if (! MAYBE_CLASS_TYPE_P (base)
+	   && ! (PACK_EXPANSION_P (base)
+		 && PACK_EXPANSION_INDEX (base)))
     {
       error ("%qT is not a class type", base);
       result = NULL_TREE;
@@ -4837,34 +4841,66 @@  finish_underlying_type (tree type)
   return underlying_type;
 }
 
-/* Implement the __type_pack_element keyword: Return the type
-   at index IDX within TYPES.  */
+/* Return the type at index IDX within TYPES.  */
 
 static tree
-finish_type_pack_element (tree idx, tree types, tsubst_flags_t complain)
+get_vec_elt_checking (tree idx, tree types, bool pack_index_p,
+		      tsubst_flags_t complain)
 {
   idx = maybe_constant_value (idx);
   if (TREE_CODE (idx) != INTEGER_CST || !INTEGRAL_TYPE_P (TREE_TYPE (idx)))
     {
       if (complain & tf_error)
-	error ("%<__type_pack_element%> index is not an integral constant");
+	{
+	  if (pack_index_p)
+	    error ("pack index is not an integral constant");
+	  else
+	    error ("%<__type_pack_element%> index is not an integral constant");
+	}
       return error_mark_node;
     }
   if (tree_int_cst_sgn (idx) < 0)
     {
       if (complain & tf_error)
-	error ("%<__type_pack_element%> index is negative");
+	{
+	  if (pack_index_p)
+	    error ("pack index is negative");
+	  else
+	    error ("%<__type_pack_element%> index is negative");
+	}
       return error_mark_node;
     }
   if (wi::to_widest (idx) >= TREE_VEC_LENGTH (types))
     {
       if (complain & tf_error)
-	error ("%<__type_pack_element%> index is out of range");
+	{
+	  if (pack_index_p)
+	    error ("pack index is out of range");
+	  else
+	    error ("%<__type_pack_element%> index is out of range");
+	}
       return error_mark_node;
     }
   return TREE_VEC_ELT (types, tree_to_shwi (idx));
 }
 
+/* Implement the __type_pack_element keyword: Return the type
+   at index IDX within TYPES.  */
+
+static tree
+finish_type_pack_element (tree idx, tree types, tsubst_flags_t complain)
+{
+  return get_vec_elt_checking (idx, types, /*pack_index_p=*/false, complain);
+}
+
+/* In a pack-index T...[N], return the element at index IDX within TYPES.  */
+
+tree
+pack_index_element (tree idx, tree types, tsubst_flags_t complain)
+{
+  return get_vec_elt_checking (idx, types, /*pack_index_p=*/true, complain);
+}
+
 /* Implement the __direct_bases keyword: Return the direct base classes
    of type.  */
 
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index c80ee068958..8637ab06d77 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -1441,7 +1441,10 @@  cp_build_qualified_type (tree type, int type_quals,
       tree t = PACK_EXPANSION_PATTERN (type);
 
       t = cp_build_qualified_type (t, type_quals, complain);
-      return make_pack_expansion (t, complain);
+      /* Regenerate the pack expansion with a qualified type.  */
+      t = make_pack_expansion (t, complain);
+      PACK_EXPANSION_INDEX (t) = PACK_EXPANSION_INDEX (type);
+      return t;
     }
 
   /* A reference or method type shall not be cv-qualified.
@@ -4242,6 +4245,9 @@  cp_tree_equal (tree t1, tree t2)
       if (!comp_template_args (PACK_EXPANSION_EXTRA_ARGS (t1),
 			       PACK_EXPANSION_EXTRA_ARGS (t2)))
 	return false;
+      if (!cp_tree_equal (PACK_EXPANSION_INDEX (t1),
+			  PACK_EXPANSION_INDEX (t2)))
+	return false;
       return true;
 
     case COMPONENT_REF:
@@ -5575,12 +5581,16 @@  cp_walk_subtrees (tree *tp, int *walk_subtrees_p, walk_tree_fn func,
     case TYPE_PACK_EXPANSION:
       WALK_SUBTREE (TREE_TYPE (t));
       WALK_SUBTREE (PACK_EXPANSION_EXTRA_ARGS (t));
+      if (PACK_EXPANSION_INDEX (t))
+	WALK_SUBTREE (PACK_EXPANSION_INDEX (t));
       *walk_subtrees_p = 0;
       break;
-      
+
     case EXPR_PACK_EXPANSION:
       WALK_SUBTREE (TREE_OPERAND (t, 0));
       WALK_SUBTREE (PACK_EXPANSION_EXTRA_ARGS (t));
+      if (PACK_EXPANSION_INDEX (t))
+	WALK_SUBTREE (PACK_EXPANSION_INDEX (t));
       *walk_subtrees_p = 0;
       break;
 
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 71d879abef1..4f0d1bc8af2 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -1620,7 +1620,9 @@  structural_comptypes (tree t1, tree t2, int strict)
       return (same_type_p (PACK_EXPANSION_PATTERN (t1),
 			   PACK_EXPANSION_PATTERN (t2))
 	      && comp_template_args (PACK_EXPANSION_EXTRA_ARGS (t1),
-				     PACK_EXPANSION_EXTRA_ARGS (t2)));
+				     PACK_EXPANSION_EXTRA_ARGS (t2))
+	      && cp_tree_equal (PACK_EXPANSION_INDEX (t1),
+				PACK_EXPANSION_INDEX (t2)));
 
     case DECLTYPE_TYPE:
       if (DECLTYPE_TYPE_ID_EXPR_OR_MEMBER_ACCESS_P (t1)
diff --git a/gcc/testsuite/g++.dg/cpp26/pack-indexing1.C b/gcc/testsuite/g++.dg/cpp26/pack-indexing1.C
new file mode 100644
index 00000000000..9d72c6582af
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/pack-indexing1.C
@@ -0,0 +1,102 @@ 
+// P2662R3 - Pack Indexing
+// PR c++/113798
+// { dg-do compile { target c++17 } }
+// { dg-options "" }
+
+template<class, class> struct same_type;
+template<class T> struct same_type<T, T> {};
+
+template<int I, typename... Ts>
+using Type = Ts...[I]; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+
+template<int I, auto... Ts>
+constexpr auto Var = Ts...[I]; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+
+template <int I, auto...Ts>
+decltype(Ts...[I])  // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+foo ()
+{
+  return Ts...[I]; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+}
+
+template<typename... Ts>
+struct S {
+  Ts...[0] a; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+#if __cpp_concepts >= 201907L
+  void foo (auto... Vs) {
+    decltype(Vs...[1]) d1 = Vs...[1]; // { dg-warning "pack indexing only available with" "" { target { c++20 && c++23_down } } }
+  }
+#endif
+};
+
+int
+g ()
+{
+  using U = Type<1, char, int, float>;
+  using U = int;
+
+  constexpr auto V = Var<2, 0, 1, 42>;
+  static_assert (V == 42);
+
+  U r = foo<2, 0, 1, 42>();
+
+  return r;
+}
+
+void
+fn1 ()
+{
+  int i = 0;
+  [&i](auto... pack) {
+    // type is int
+    decltype(pack...[0]) x5 = 42; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+    // type is int&
+    [[maybe_unused]] decltype((pack...[0])) x6 = i; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+  }(0);
+}
+
+#if __cpp_concepts >= 201907L
+int
+bar (auto... pack)
+{
+  (void) pack...[0]; // { dg-warning "pack indexing only available with" "" { target { c++20 && c++23_down } } }
+  int x = pack...[0]; // { dg-warning "pack indexing only available with" "" { target { c++20 && c++23_down } } }
+  return x;
+}
+#endif
+
+template<auto...pack>
+void
+fn2 ()
+{
+  [[maybe_unused]] decltype(pack...[0]) x1; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+  [[maybe_unused]] decltype((pack...[0])) x2; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+  same_type<decltype(x1), int>();
+  same_type<decltype(x2), int>();
+}
+
+template<typename... T>
+void
+fn3 (int p)
+{
+  T...[0] a = p; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+  (T...[0])(a);  // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+}
+
+template<int... Is>
+void fn4 ()
+{
+  same_type<decltype(Is...[0]), int>();	// { dg-warning "pack indexing only available with" "" { target c++23_down } }
+  same_type<decltype((Is...[0])), int>(); // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+}
+
+void
+g3 ()
+{
+  fn2<0>();
+#if __cpp_concepts >= 201907L
+  bar (0);
+#endif
+  S<int> s;
+  fn4<0, 1, 2>();
+}
diff --git a/gcc/testsuite/g++.dg/cpp26/pack-indexing2.C b/gcc/testsuite/g++.dg/cpp26/pack-indexing2.C
new file mode 100644
index 00000000000..c3c67d6ea34
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/pack-indexing2.C
@@ -0,0 +1,111 @@ 
+// P2662R3 - Pack Indexing
+// PR c++/113798
+// { dg-do compile { target c++26 } }
+// Test invalid cases.
+
+template<int I, typename... Ts>
+using Type = Typo...[I]; // { dg-error "does not name a type" }
+
+template<int I, auto... Ts>
+constexpr auto Var = Typo...[I]; // { dg-error "no parameter packs" }
+
+template<typename... Ts>
+void foo(Ts...[]);  // { dg-error "pack index missing" }
+
+template <typename... Ts>
+void f(Ts...[1]);
+
+template<int... N>
+int f2 (X... [N]); // { dg-error "contains no parameter packs" }
+
+struct X;
+
+template<typename T>
+struct TX;
+
+template <typename T, auto V, template<typename> typename Tp>
+void
+bad (int i)
+{
+  i...[0];  // { dg-error "no parameter packs" }
+  V...[0];  // { dg-error "no parameter packs" }
+  X...[0] x;  // { dg-error "no parameter packs" }
+  T...[0] t;  // { dg-error "no parameter packs" }
+  Tp...[0] tp;  // { dg-error "expected" }
+
+  X...[0] xarr[1];  // { dg-error "no parameter packs" }
+  T...[0] tarr[1];  // { dg-error "no parameter packs" }
+  Tp...[0] tparr[1];  // { dg-error "expected" }
+}
+
+template<int N>
+int
+getT (auto... Ts)
+{
+  return Ts...[N]; // { dg-error "pack index is out of range" }
+}
+
+template<int N>
+int
+getT2 (auto... Ts)
+{
+  return Ts...[N]; // { dg-error "pack index is negative" }
+}
+
+template<auto N, typename... Ts>
+void
+badtype ()
+{
+  Ts...[N] t; // { dg-error "pack index is out of range" }
+}
+
+template<auto N, typename... Ts>
+void
+badtype2 ()
+{
+  Ts...[N] t; // { dg-error "pack index is negative" }
+}
+
+int nonconst () { return 42; }
+
+template<typename... Ts>
+void
+badindex ()
+{
+  Ts...[nonconst ()] t; // { dg-error "pack index is not an integral constant" }
+  // { dg-error "non-.constexpr. function" "" { target *-*-* } .-1 }
+}
+
+template<typename... Ts>
+struct broken {
+  Ts...1;  // { dg-error "expected" }
+  Ts...[;  // { dg-error "invalid" }
+  Ts...[1;  // { dg-error "invalid" }
+  Ts...[];  // { dg-error "pack index missing" }
+
+  void foo (auto...Vs) {
+    decltype(Vs...[1]) d1 = Vs...[]; // { dg-error "pack index missing" }
+    decltype(Vs...[1]) d2 = Vs...[; // { dg-error "expected" }
+  }
+};
+
+int main()
+{
+   // void f<int, double>(int [1], double [1])
+  f<int, double>(nullptr, nullptr); // { dg-error "cannot convert" }
+  bad<int, 0, TX>(42);
+
+  getT<0>(); // { dg-message "required from here" }
+  getT<1>();  // { dg-message "required from here" }
+  getT2<-1>();  // { dg-message "required from here" }
+
+  badtype<0>(); // { dg-message "required from here" }
+  badtype<1, int>(); // { dg-message "required from here" }
+  badtype2<-1>(); // { dg-message "required from here" }
+  badtype2<-1, int>(); // { dg-message "required from here" }
+
+  badindex<int, int, int>();
+
+  bool b = nothere...[0]; // { dg-error "no parameter packs" }
+  using E = nothere...[0]; // { dg-error "does not name a type" }
+}
diff --git a/gcc/testsuite/g++.dg/cpp26/pack-indexing3.C b/gcc/testsuite/g++.dg/cpp26/pack-indexing3.C
new file mode 100644
index 00000000000..8c10b307f3a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/pack-indexing3.C
@@ -0,0 +1,41 @@ 
+// P2662R3 - Pack Indexing
+// PR c++/113798
+// { dg-do compile { target c++26 } }
+// From LLVM's cxx2c-pack-indexing.cpp.
+
+template<typename...>
+struct X { };
+
+template<typename... T>
+requires requires(T...[0]) { {T...[0](0)}; }
+struct S : T...[1] {
+  [[maybe_unused]] T...[1] base = {};
+  using foo = T...[1];
+  S() : T...[1]() { }
+  X<T...[0]> x;
+  const T...[0] f(T...[0]&& parm) noexcept((T...[0])0) {
+    T...[0] (*test)(const volatile T...[0]**);
+    thread_local T...[0] d;
+    [[maybe_unused]] T...[0] a = parm;
+    auto ptr = new T...[0](0);
+    (*ptr).~T...[0]();
+    return T...[0](0);
+    typename T...[1]::foo b = 0;
+    T...[1]::i = 0;
+    return (T...[0])(a);
+    new T...[0];
+    [[maybe_unused]] auto l = []<T...[0]>(T...[0][1]) -> T...[0]{ return {}; };
+    [[maybe_unused]] auto _ = l.template operator()<T...[0]{}>({0});
+  }
+  operator T...[0]() const { }
+};
+
+struct base {
+    using foo = int;
+    static inline int i = 42;
+};
+
+int main()
+{
+  S<int, base>().f(0);
+}
diff --git a/gcc/testsuite/g++.dg/cpp26/pack-indexing4.C b/gcc/testsuite/g++.dg/cpp26/pack-indexing4.C
new file mode 100644
index 00000000000..8decf3064bc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/pack-indexing4.C
@@ -0,0 +1,65 @@ 
+// P2662R3 - Pack Indexing
+// PR c++/113798
+// { dg-do compile { target c++26 } }
+// From LLVM's cxx2c-pack-indexing.cpp.
+
+template <class, class>
+constexpr bool is_same = false;
+template <class T>
+constexpr bool is_same<T, T> = true;
+
+template <typename T>
+constexpr bool
+f (auto&&... p)
+{
+  return is_same<T, decltype(p...[0])>;
+}
+
+void
+g ()
+{
+    int a = 0;
+    const int b = 0;
+    static_assert(f<int&&>(0));
+    static_assert(f<int&>(a));
+    static_assert(f<const int&>(b));
+}
+
+template<auto... p>
+struct A {
+  enum E {
+    x = p...[0]
+  };
+};
+static_assert(A<42>::x == 42);
+
+struct S { };
+template<auto... p>
+constexpr auto constant_initializer = p...[0];
+constexpr auto InitOk = constant_initializer<S{}>;
+
+consteval int evaluate(auto... p) {
+    return p...[0];
+}
+constexpr int x = evaluate(42, S{});
+static_assert(x == 42);
+
+template <auto... Is>
+struct IL{};
+
+template <typename... Ts>
+struct TL{};
+
+template <typename Tl, typename Il>
+struct SpliceImpl;
+
+template <typename... Ts, auto... Is>
+struct SpliceImpl<TL<Ts...>, IL<Is...>>
+{
+    using type = TL<Ts...[Is]...>;
+};
+
+template <typename Tl, typename Il>
+using Splice = typename SpliceImpl<Tl, Il>::type;
+using type = Splice<TL<char, short, long, double>, IL<1, 2>>;
+static_assert(is_same<type, TL<short, long>>);
diff --git a/gcc/testsuite/g++.dg/cpp26/pack-indexing5.C b/gcc/testsuite/g++.dg/cpp26/pack-indexing5.C
new file mode 100644
index 00000000000..901956e2dae
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp26/pack-indexing5.C
@@ -0,0 +1,41 @@ 
+// P2662R3 - Pack Indexing
+// PR c++/113798
+// { dg-do compile { target c++26 } }
+
+template<class, class> struct same_type;
+template<class T> struct same_type<T, T> {};
+
+void
+fn1 (auto... Ts)
+{
+  same_type<decltype(Ts...[0]), int>();
+  same_type<decltype((Ts...[0])), int&>();
+  same_type<decltype(Ts...[1]), unsigned int>();
+  same_type<decltype((Ts...[1])), unsigned int&>();
+}
+
+template<auto... Is>
+void
+fn2 ()
+{
+  same_type<decltype(Is...[0]), int>();
+  same_type<decltype((Is...[0])), int>();
+  same_type<decltype(Is...[1]), unsigned int>();
+  same_type<decltype((Is...[1])), unsigned int>();
+  same_type<decltype(Is...[2]), double>();
+  same_type<decltype((Is...[2])), double>();
+  same_type<decltype(Is...[3]), float>();
+  same_type<decltype((Is...[3])), float>();
+  same_type<decltype(Is...[4]), unsigned char>();
+  same_type<decltype((Is...[4])), unsigned char>();
+}
+
+static constexpr unsigned char c = 'A';
+
+void
+g ()
+{
+  int i = 42;
+  fn1 (i, 42u);
+  fn2<0, 1u, 2.0, 3.f, c>();
+}