public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] c++: Define built-in for std::tuple_element [PR100157]
@ 2022-07-07 17:14 Jonathan Wakely
  2022-07-07 17:25 ` Marek Polacek
                   ` (2 more replies)
  0 siblings, 3 replies; 14+ messages in thread
From: Jonathan Wakely @ 2022-07-07 17:14 UTC (permalink / raw)
  To: libstdc++, gcc-patches; +Cc: Jason Merrill, Marek Polacek

This adds a new built-in to replace the recursive class template
instantiations done by traits such as std::tuple_element and
std::variant_alternative. The purpose is to select the Nth type from a
list of types, e.g. __builtin_type_pack_element(1, char, int, float) is
int.

For a pathological example tuple_element_t<1000, tuple<2000 types...>>
the compilation time is reduced by more than 90% and the memory  used by
the compiler is reduced by 97%. In realistic examples the gains will be
much smaller, but still relevant.

Clang has a similar built-in, __type_pack_element<N, T...>, but that's a
"magic template" built-in using <> syntax, which GCC doesn't support. So
this provides an equivalent feature, but as a built-in function using
parens instead of <>. I don't really like the name "type pack element"
(it gives you an element from a pack of types) but the semi-consistency
with Clang seems like a reasonable argument in favour of keeping the
name. I'd be open to alternative names though, e.g. __builtin_nth_type
or __builtin_type_at_index.


The patch has some problems though ...

FIXME 1: Marek pointed out that this this ICEs:
template<class... T> using type = __builtin_type_pack_element(sizeof(T), T...);
type<int, char> c;

The sizeof(T) expression is invalid, because T is an unexpanded pack,
but it's not rejected and instead crashes:

ice.C: In substitution of 'template<class ... T> using type = __builtin_type_pack_element (sizeof (T), T ...) [with T = {int, char}]':
ice.C:2:15:   required from here
ice.C:1:63: internal compiler error: in dependent_type_p, at cp/pt.cc:27490
    1 | template<class... T> using type = __builtin_type_pack_element(sizeof(T), T...);
      |                                                               ^~~~~~~~~
0xe13eea dependent_type_p(tree_node*)
        /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:27490
0xeb1286 cxx_sizeof_or_alignof_type(unsigned int, tree_node*, tree_code, bool, bool)
        /home/jwakely/src/gcc/gcc/gcc/cp/typeck.cc:1912
0xdf4fcc tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
        /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:20582
0xdd9121 tsubst_tree_list(tree_node*, tree_node*, int, tree_node*)
        /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15587
0xddb583 tsubst(tree_node*, tree_node*, int, tree_node*)
        /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:16056
0xddcc9d tsubst(tree_node*, tree_node*, int, tree_node*)
        /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:16436
0xdd6d45 tsubst_decl
        /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15038
0xdd952a tsubst(tree_node*, tree_node*, int, tree_node*)
        /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15668
0xdfb9a1 instantiate_template(tree_node*, tree_node*, int)
        /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:21811
0xdfc1b6 instantiate_alias_template
        /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:21896
0xdd9796 tsubst(tree_node*, tree_node*, int, tree_node*)
        /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15696
0xdbaba5 lookup_template_class(tree_node*, tree_node*, tree_node*, tree_node*, int, int)
        /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:10131
0xe4bac0 finish_template_type(tree_node*, tree_node*, int)
        /home/jwakely/src/gcc/gcc/gcc/cp/semantics.cc:3727
0xd334c8 cp_parser_template_id
        /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:18458
0xd429b0 cp_parser_class_name
        /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:25923
0xd1ade9 cp_parser_qualifying_entity
        /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:7193
0xd1a2c8 cp_parser_nested_name_specifier_opt
        /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:6875
0xd4eefd cp_parser_template_introduction
        /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:31668
0xd4f416 cp_parser_template_declaration_after_export
        /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:31840
0xd2d60e cp_parser_declaration
        /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:15083


FIXME 2: I want to mangle __builtin_type_pack_element(N, T...) the same as
typename std::_Nth_type<N, T...>::type but I don't know how. Instead of
trying to fake the mangled string, it's probably better to build a decl
for that nested type, right? Any suggestions where to find something
similar I can learn from?

The reason to mangle it that way is that it preserves the same symbol
names as the library produced in GCC 12, and that it will still produce
with non-GCC compilers (see the definitions of std::_Nth_type in the
library parts of the patch). If we don't do that then either we need to
ensure it never appears in a mangled name, or define some other
GCC-specific mangling for this built-in (e.g. we could just mangle it as
though it's a function, "19__builtin_type_pack_elementELm1EJDpT_E" or
something like that!). If we ensure it doesn't appear in mangled names
that means we still need to instantiate the _Nth_type class template,
rather than defining the alias template _Nth_type_t to use the built-in
directly.  That loses a little of the compilation performance gain that
comes from defining the built-in in the first place (although avoid the
recusrion is the biggest gain, which we'd still get).



FIXME 3:This change regresses diagnostics from std::tuple_element
because as well as the existing:

tuple:1357: error: static assertion failed: tuple index must be in range

we now also get:

bits/utility.h:231: error: '__builtin_type_pack_element' index is out of range

which is not very user-friendly.

Maybe that should not mention the built-in by name, and just say
something generic like "out of range index into type pack". Or maybe
diagnose it as std::_Nth_type_t, like I plan to mangle it. That would be
a bit confusing for anybody using the built-in directly, but I'm sure
they can live with it - they're already using a non-portable intrinsic.

That still means two errors where we used to only print one though.
Maybe the library needs to try harder to not let invalid indices reach
the built-in. Currently we have:

  template<size_t __i, typename... _Types>
    struct tuple_element<__i, tuple<_Types...>>
    {
      static_assert(__i < sizeof...(_Types), "tuple index must be in range");

      using type = _Nth_type_t<__i, _Types...>;
    };

And _Nth_type_t is an alias for a type defined using the built-in.

Maybe I need to add another level of indirection (losing some of the
compile-time improvements that the built-in is supposed to provide) or
use concepts to constrain _Nth_type_t (which doesn't help pre-C++20).

This seems like another case where the compiler should just stop when a
static assert fails. The whole point of that assertion is that what
follows doesn't make sense unless it passes.


Suggestions for how to fix these issues are welcome, I think I've gone
as far as I can for now.


-- >8 --

Add __builtin_type_pack_element so that std::tuple_element and
std::variant_alternative can be implemented efficiently, without
recursive class template instantiations.

The name is intended to be similar to Clang's __type_pack_element<>
built-in which has the same behaviour, but uses template syntax instead
of function-call syntax.

The libstdc++ headers can be updated to use this new built-in, or
Clang's equivalent. Until the FIXME in mangle.cc:write_type is fixed
(i.e. mangling for the built-in is added) we can't defined the
_Nth_type_t alias template to use the built-in directly, because
function templates that use the alias would need to mangle the built-in.
I suggest mangling it as typename _Nth_type<N, T...>::type which will be
backwards compatible with symbol names from GCC 12 and Clang.

	PR c++/100157

gcc/c-family/ChangeLog:

	* c-common.cc (c_common_reswords): Add
	__builtin_type_pack_element.
	* c-common.h (enum rid): Add RID_TYPE_PACK_ELEMENT.

gcc/cp/ChangeLog:

	* constraint.cc (diagnose_trait_expr): Add
	CPTK_TYPE_PACK_ELEMENT to switch.
	* cp-objcp-common.cc (names_builtin_p): Add
	RID_TYPE_PACK_ELEMENT to switch.
	(cp_common_init_ts): Mark TYPE_PACK_ELEMENT as not having a
	common member.
	* cp-tree.def (TYPE_PACK_ELEMENT): Define tree code.
	* cp-tree.h (enum cp_trait_kind): Add CPTK_TYPE_PACK_ELEMENT.
	(TYPE_PACK_ELEMENT_ARGS): Define.
	(finish_type_pack_element): Declare.
	* error.cc (dump_type): Add TYPE_PACK_ELEMENT to switch.
	(dump_type_prefix): Likewise.
	(dump_type_suffix): Likewise.
	* mangle.cc (write_type): Likewise.
	* parser.cc (cp_keyword_starts_decl_specifier_p): Add
	RID_TYPE_PACK_ELEMENT to switch.
	(cp_parser_trait_expr): Likewise. Parse its arguments and call
	finish_type_pack_element.
	(cp_parser_simple_type_specifier): Add RID_TYPE_PACK_ELEMENT to
	switch.
	* pt.cc (for_each_template_parm_r): Add TYPE_PACK_ELEMENT to
	switch.
	(tsubst): Likewise.
	(unify): Likewise.
	(dependent_type_p_r): A TYPE_PACK_ELEMENT is dependent.
	* semantics.cc (finish_type_pack_element): New function.

gcc/ChangeLog:

	* doc/extend.texi (Type Traits): Document new built-in.

libstdc++-v3/ChangeLog:

	* include/bits/utility.h (_Nth_type_t): New alias template using
	built-ins when available.
	* include/std/tuple: Use _Nth_type_t instead of _Nth_type.
	* include/std/variant: Likewise.
	* testsuite/20_util/tuple/element_access/get_neg.cc: Prune
	additional errors from the new built-in.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/type_pack_element1.C: New test.
	* g++.dg/ext/type_pack_element2.C: New test.
---
 gcc/c-family/c-common.cc                      |  1 +
 gcc/c-family/c-common.h                       |  1 +
 gcc/cp/constraint.cc                          |  1 +
 gcc/cp/cp-objcp-common.cc                     |  2 +
 gcc/cp/cp-tree.def                            |  4 ++
 gcc/cp/cp-tree.h                              |  6 +++
 gcc/cp/error.cc                               | 16 +++++++
 gcc/cp/mangle.cc                              |  5 +++
 gcc/cp/parser.cc                              | 39 ++++++++++++++--
 gcc/cp/pt.cc                                  | 18 ++++++--
 gcc/cp/semantics.cc                           | 44 +++++++++++++++++++
 gcc/doc/extend.texi                           |  6 +++
 gcc/testsuite/g++.dg/ext/type_pack_element1.C | 26 +++++++++++
 gcc/testsuite/g++.dg/ext/type_pack_element2.C | 31 +++++++++++++
 libstdc++-v3/include/bits/utility.h           | 26 +++++++++++
 libstdc++-v3/include/std/tuple                |  2 +-
 libstdc++-v3/include/std/variant              | 24 +++++-----
 .../20_util/tuple/element_access/get_neg.cc   |  1 +
 18 files changed, 232 insertions(+), 21 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element1.C
 create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element2.C

diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 1b8e73f7bc5..655b571a4ee 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -389,6 +389,7 @@ const struct c_common_resword c_common_reswords[] =
   { "__builtin_shufflevector", RID_BUILTIN_SHUFFLEVECTOR, 0 },
   { "__builtin_tgmath", RID_BUILTIN_TGMATH, D_CONLY },
   { "__builtin_offsetof", RID_OFFSETOF, 0 },
+  { "__builtin_type_pack_element", RID_TYPE_PACK_ELEMENT, D_CXXONLY },
   { "__builtin_types_compatible_p", RID_TYPES_COMPATIBLE_P, D_CONLY },
   { "__builtin_va_arg",	RID_VA_ARG,	0 },
   { "__complex",	RID_COMPLEX,	0 },
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index c0900848965..e9d0864f2ba 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -184,6 +184,7 @@ enum rid
   RID_IS_UNION,                RID_UNDERLYING_TYPE,
   RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
   RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
+  RID_TYPE_PACK_ELEMENT,
 
   /* C++11 */
   RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, RID_STATIC_ASSERT,
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 591155cee22..f7bd189ae47 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3690,6 +3690,7 @@ diagnose_trait_expr (tree expr, tree args)
     case CPTK_BASES:
     case CPTK_DIRECT_BASES:
     case CPTK_UNDERLYING_TYPE:
+    case CPTK_TYPE_PACK_ELEMENT:
       /* We shouldn't see these non-expression traits.  */
       gcc_unreachable ();
     /* We deliberately omit the default case so that when adding a new
diff --git a/gcc/cp/cp-objcp-common.cc b/gcc/cp/cp-objcp-common.cc
index 0b70d5567e4..bac6b5a6214 100644
--- a/gcc/cp/cp-objcp-common.cc
+++ b/gcc/cp/cp-objcp-common.cc
@@ -461,6 +461,7 @@ names_builtin_p (const char *name)
     case RID_IS_ASSIGNABLE:
     case RID_IS_CONSTRUCTIBLE:
     case RID_UNDERLYING_TYPE:
+    case RID_TYPE_PACK_ELEMENT:
       return true;
     default:
       break;
@@ -517,6 +518,7 @@ cp_common_init_ts (void)
   MARK_TS_TYPE_NON_COMMON (TEMPLATE_TEMPLATE_PARM);
   MARK_TS_TYPE_NON_COMMON (TEMPLATE_TYPE_PARM);
   MARK_TS_TYPE_NON_COMMON (TYPE_PACK_EXPANSION);
+  MARK_TS_TYPE_NON_COMMON (TYPE_PACK_ELEMENT);
 
   /* Statements.  */
   MARK_TS_EXP (CLEANUP_STMT);
diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def
index f9cbd339f19..6ea197f16af 100644
--- a/gcc/cp/cp-tree.def
+++ b/gcc/cp/cp-tree.def
@@ -470,6 +470,10 @@ DEFTREECODE (DECLTYPE_TYPE, "decltype_type", tcc_type, 0)
    UNDERLYING_TYPE_TYPE is the type in question.  */
 DEFTREECODE (UNDERLYING_TYPE, "underlying_type", tcc_type, 0)
 
+/* A type designated by `__builtin_type_pack_element (n, type)'.
+   TYPE_PACK_ELEMENT_ARGS contains the arguments.  */
+DEFTREECODE (TYPE_PACK_ELEMENT, "builtin_type_pack_element", tcc_type, 0)
+
 /* A type designated by one of the bases type traits.
    BASES_TYPE is the type in question.  */
 DEFTREECODE (BASES, "bases", tcc_type, 0)
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 2fde4f83b41..2430b0b94c1 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -1393,6 +1393,7 @@ enum cp_trait_kind
   CPTK_IS_TRIVIALLY_CONSTRUCTIBLE,
   CPTK_IS_TRIVIALLY_COPYABLE,
   CPTK_IS_UNION,
+  CPTK_TYPE_PACK_ELEMENT,
   CPTK_UNDERLYING_TYPE,
   CPTK_IS_ASSIGNABLE,
   CPTK_IS_CONSTRUCTIBLE,
@@ -4789,6 +4790,10 @@ get_vec_init_expr (tree t)
 #define UNDERLYING_TYPE_TYPE(NODE) \
   (TYPE_VALUES_RAW (UNDERLYING_TYPE_CHECK (NODE)))
 
+/* The arguments for a TYPE_PACK_ELEMENT.  */
+#define TYPE_PACK_ELEMENT_ARGS(NODE) \
+  (TYPE_VALUES_RAW (TYPE_PACK_ELEMENT_CHECK (NODE)))
+
 /* The type in question for BASES.  */
 #define BASES_TYPE(NODE) \
   (TYPE_VALUES_RAW (BASES_CHECK (NODE)))
@@ -7640,6 +7645,7 @@ extern cp_expr finish_id_expression		(tree, tree, tree,
                                                  location_t);
 extern tree finish_typeof			(tree);
 extern tree finish_underlying_type	        (tree);
+extern tree finish_type_pack_element	        (tree, tree);
 extern tree calculate_bases                     (tree, tsubst_flags_t);
 extern tree finish_bases                        (tree, bool);
 extern tree calculate_direct_bases              (tree, tsubst_flags_t);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 94181e76d0e..1b6f4df51b9 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -706,6 +706,20 @@ dump_type (cxx_pretty_printer *pp, tree t, int flags)
       pp_cxx_right_paren (pp);
       break;
 
+    case TYPE_PACK_ELEMENT:
+      t = TYPE_PACK_ELEMENT_ARGS (t);
+      pp_cxx_ws_string (pp, "__builtin_type_pack_element");
+      pp_cxx_whitespace (pp);
+      pp_cxx_left_paren (pp);
+      dump_expr (pp, TREE_VALUE (t), flags & ~TFF_EXPR_IN_PARENS);
+      for (tree arg = TREE_CHAIN (t); arg; arg = TREE_CHAIN (arg))
+	{
+	  pp_separate_with_comma (pp);
+	  dump_type (pp, TREE_VALUE (arg), flags);
+	}
+      pp_cxx_right_paren (pp);
+      break;
+
     case TYPE_PACK_EXPANSION:
       dump_type (pp, PACK_EXPANSION_PATTERN (t), flags);
       pp_cxx_ws_string (pp, "...");
@@ -974,6 +988,7 @@ dump_type_prefix (cxx_pretty_printer *pp, tree t, int flags)
     case UNDERLYING_TYPE:
     case DECLTYPE_TYPE:
     case TYPE_PACK_EXPANSION:
+    case TYPE_PACK_ELEMENT:
     case FIXED_POINT_TYPE:
     case NULLPTR_TYPE:
       dump_type (pp, t, flags);
@@ -1098,6 +1113,7 @@ dump_type_suffix (cxx_pretty_printer *pp, tree t, int flags)
     case UNDERLYING_TYPE:
     case DECLTYPE_TYPE:
     case TYPE_PACK_EXPANSION:
+    case TYPE_PACK_ELEMENT:
     case FIXED_POINT_TYPE:
     case NULLPTR_TYPE:
       break;
diff --git a/gcc/cp/mangle.cc b/gcc/cp/mangle.cc
index 75388e99bfd..fbaed426940 100644
--- a/gcc/cp/mangle.cc
+++ b/gcc/cp/mangle.cc
@@ -2393,6 +2393,11 @@ write_type (tree type)
 	      sorry ("mangling %<__underlying_type%>");
 	      break;
 
+	    case TYPE_PACK_ELEMENT:
+	      /* FIXME: Mangle as std::_Nth_type<N, T...>::type.  */
+	      sorry ("mangling %<__builtin_type_pack_element%>");
+	      break;
+
 	    case LANG_TYPE:
 	      /* fall through.  */
 
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index bf9ea3685f8..8f0f0331d4f 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -1142,6 +1142,7 @@ cp_keyword_starts_decl_specifier_p (enum rid keyword)
       /* C++11 extensions.  */
     case RID_DECLTYPE:
     case RID_UNDERLYING_TYPE:
+    case RID_TYPE_PACK_ELEMENT:
     case RID_CONSTEXPR:
       /* C++20 extensions.  */
     case RID_CONSTINIT:
@@ -10966,6 +10967,10 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
     case RID_UNDERLYING_TYPE:
       kind = CPTK_UNDERLYING_TYPE;
       break;
+    case RID_TYPE_PACK_ELEMENT:
+      kind = CPTK_TYPE_PACK_ELEMENT;
+      variadic = true;
+      break;
     case RID_BASES:
       kind = CPTK_BASES;
       break;
@@ -11001,10 +11006,24 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
   matching_parens parens;
   parens.require_open (parser);
 
-  {
-    type_id_in_expr_sentinel s (parser);
-    type1 = cp_parser_type_id (parser);
-  }
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    {
+      cp_expr e = cp_parser_constant_expression (parser, 0, nullptr, true);
+      if (!cp_lexer_next_token_is (parser->lexer, CPP_COMMA))
+	{
+	  location_t err_loc = cp_lexer_peek_token (parser->lexer)->location;
+	  err_loc = make_location (err_loc, start_loc, err_loc);
+	  error_at (err_loc, "%<__builtin_type_pack_element%> requires"
+			      " one or more type arguments");
+	  return error_mark_node;
+	}
+      type1 = e.get_value (); // actually a constant-expression, not a type
+    }
+  else
+    {
+      type_id_in_expr_sentinel s (parser);
+      type1 = cp_parser_type_id (parser);
+    }
 
   if (type1 == error_mark_node)
     return error_mark_node;
@@ -11054,6 +11073,8 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
     {
     case CPTK_UNDERLYING_TYPE:
       return cp_expr (finish_underlying_type (type1), trait_loc);
+    case CPTK_TYPE_PACK_ELEMENT:
+      return cp_expr (finish_type_pack_element (type1, type2), trait_loc);
     case CPTK_BASES:
       return cp_expr (finish_bases (type1, false), trait_loc);
     case CPTK_DIRECT_BASES:
@@ -19571,6 +19592,7 @@ cp_parser_type_specifier (cp_parser* parser,
      char16_t
      char32_t
      __underlying_type ( type-id )
+     __builtin_type_pack_element ( constant-expression , type-id , [opt] )
 
    C++17 extension:
 
@@ -19783,6 +19805,15 @@ cp_parser_simple_type_specifier (cp_parser* parser,
 
       return type;
 
+    case RID_TYPE_PACK_ELEMENT:
+      type = cp_parser_trait_expr (parser, RID_TYPE_PACK_ELEMENT);
+      if (decl_specs)
+	cp_parser_set_decl_spec_type (decl_specs, type,
+				      token,
+				      /*type_definition_p=*/false);
+
+      return type;
+
     case RID_BASES:
     case RID_DIRECT_BASES:
       type = cp_parser_trait_expr (parser, token->keyword);
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 8672da123f4..590b3eccc84 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -10592,6 +10592,7 @@ for_each_template_parm_r (tree *tp, int *walk_subtrees, void *d)
     case TYPEOF_TYPE:
     case DECLTYPE_TYPE:
     case UNDERLYING_TYPE:
+    case TYPE_PACK_ELEMENT:
       if (pfd->include_nondeduced_p
 	  && for_each_template_parm (TYPE_VALUES_RAW (t), fn, data,
 				     pfd->visited,
@@ -16430,6 +16431,15 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 	return finish_underlying_type (type);
       }
 
+    case TYPE_PACK_ELEMENT:
+      {
+	tree subst_args = tsubst (TYPE_PACK_ELEMENT_ARGS (t), args,
+				  complain, in_decl);
+	tree pack_index = TREE_VALUE (subst_args);
+	tree types = TREE_CHAIN (subst_args);
+	return finish_type_pack_element (pack_index, types);
+      }
+
     case TYPE_ARGUMENT_PACK:
     case NONTYPE_ARGUMENT_PACK:
       return tsubst_argument_pack (t, args, complain, in_decl);
@@ -24829,8 +24839,9 @@ unify (tree tparms, tree targs, tree parm, tree arg, int strict,
     case TYPEOF_TYPE:
     case DECLTYPE_TYPE:
     case UNDERLYING_TYPE:
+    case TYPE_PACK_ELEMENT:
       /* Cannot deduce anything from TYPEOF_TYPE, DECLTYPE_TYPE,
-	 or UNDERLYING_TYPE nodes.  */
+	 UNDERLYING_TYPE, or TYPE_PACK_ELEMENT nodes.  */
       return unify_success (explain_p);
 
     case ERROR_MARK:
@@ -27405,12 +27416,13 @@ dependent_type_p_r (tree type)
 	       (INNERMOST_TEMPLATE_ARGS (CLASSTYPE_TI_ARGS (type)))))
     return true;
 
-  /* All TYPEOF_TYPEs, DECLTYPE_TYPEs, and UNDERLYING_TYPEs are
+  /* All these are
      dependent; if the argument of the `typeof' expression is not
      type-dependent, then it should already been have resolved.  */
   if (TREE_CODE (type) == TYPEOF_TYPE
       || TREE_CODE (type) == DECLTYPE_TYPE
-      || TREE_CODE (type) == UNDERLYING_TYPE)
+      || TREE_CODE (type) == UNDERLYING_TYPE
+      || TREE_CODE (type) == TYPE_PACK_ELEMENT)
     return true;
 
   /* A template argument pack is dependent if any of its packed
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 2344b5eea00..36eff75ed45 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -4418,6 +4418,50 @@ finish_underlying_type (tree type)
   return underlying_type;
 }
 
+/* Implement the __builtin_type_pack_element keyword: Return the type
+   at index N in TYPES..., suitable for use as a type-specifier.  */
+
+tree
+finish_type_pack_element (tree n, tree types)
+{
+  if (n == error_mark_node
+      || types == error_mark_node)
+    return error_mark_node;
+
+  if (processing_template_decl)
+    {
+      if (value_dependent_expression_p (n) || uses_template_parms (types))
+	{
+	  tree t = cxx_make_type (TYPE_PACK_ELEMENT);
+	  TYPE_PACK_ELEMENT_ARGS (t) = tree_cons (NULL_TREE, n, types);
+	  return t;
+	}
+    }
+
+  n = fold_non_dependent_expr (n);
+
+  if (!INTEGRAL_TYPE_P (TREE_TYPE (n)) || TREE_CODE (n) != INTEGER_CST)
+    {
+      error ("%<__builtin_type_pack_element%> index is not an integral"
+	     " constant");
+      return error_mark_node;
+    }
+
+  HOST_WIDE_INT lunroll = tree_to_shwi (n);
+  if (lunroll < 0 || lunroll >= list_length (types))
+    {
+      error ("%<__builtin_type_pack_element%> index is out of range");
+      return error_mark_node;
+    }
+
+  unsigned index = (unsigned)lunroll;
+  while (index-- > 0 && types != NULL_TREE)
+    types = TREE_CHAIN (types);
+  if (types == NULL_TREE)
+    return error_mark_node;
+  return TREE_VALUE (types);
+}
+
 /* Implement the __direct_bases keyword: Return the direct base classes
    of type.  */
 
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index dfbe33ac652..5f0f39fe72e 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -25201,6 +25201,12 @@ definition, expands to a template argument pack containing integers
 from @code{0} to @code{length-1}.  This is provided for efficient
 implementation of @code{std::make_integer_sequence}.
 
+@item __builtin_type_pack_element (n, types...)
+The Nth type in the list of types.
+This is provided for efficient
+implementation of @code{std::tuple_element} and similar.
+Requires: 0 @leq{} @code{n} < number of type arguments.
+
 @end table
 
 
diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element1.C b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
new file mode 100644
index 00000000000..cc4b6b4b67f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
@@ -0,0 +1,26 @@
+// { dg-do compile { target c++11 } }
+
+template<class, class> struct is_same { static constexpr bool value = false; };
+template<class T> struct is_same<T,T> { static constexpr bool value = true; };
+
+static_assert( is_same<__builtin_type_pack_element(0, int), int>::value, "" );
+static_assert( is_same<__builtin_type_pack_element(0, long), long>::value, "" );
+static_assert( is_same<__builtin_type_pack_element(1, float, char, int), char>::value, "" );
+
+using T = __builtin_type_pack_element(sizeof('0'), int, int);
+
+template<int N, class... T>
+using Nth_type = __builtin_type_pack_element(N, T...);
+
+static_assert( is_same<Nth_type<0, int>, int>::value, "" );
+static_assert( is_same<Nth_type<0, long>, long>::value, "" );
+static_assert( is_same<Nth_type<1, float, char, int>, char>::value, "" );
+
+template<int N>
+struct Nth_type_class_template
+{
+  using type = __builtin_type_pack_element(N, int, void, char, float, long);
+};
+
+static_assert( is_same<typename Nth_type_class_template<0>::type, int>::value, "" );
+static_assert( is_same<typename Nth_type_class_template<1>::type, void>::value, "" );
diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element2.C b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
new file mode 100644
index 00000000000..04040e0e84f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
@@ -0,0 +1,31 @@
+// { dg-do compile { target c++11 } }
+// { dg-options -w }
+
+using X1 = __builtin_type_pack_element(1);  // { dg-error "one or more type" }
+using X2 = __builtin_type_pack_element(1, 1); // { dg-error "expected type" }
+int i;
+using X3 = __builtin_type_pack_element(1, i); // { dg-error "does not name a type" }
+using X4 = __builtin_type_pack_element(1, int); // { dg-error "out of range" }
+
+using X5 = __builtin_type_pack_element(-1, int); // { dg-error "out of range" }
+using X6 = __builtin_type_pack_element(nullptr, int); // { dg-error "integral" }
+
+template<int N, class T>
+struct uninstantiated_template
+{
+  using X = __builtin_type_pack_element(2, int); // { dg-error "out of range" }
+  using Y = __builtin_type_pack_element(2, T);   // { dg-bogus "out of range" }
+  using Z = __builtin_type_pack_element(N, int); // { dg-bogus "." }
+};
+
+
+template<int N, class T>
+struct instantiated_template
+{
+  using Y = __builtin_type_pack_element(2, T); // { dg-error "out of range" }
+  using Z = __builtin_type_pack_element(N, T); // { dg-error "out of range" }
+};
+
+using Y = typename instantiated_template<0, int>::Y;
+using Z = typename instantiated_template<1, int>::Z;
+// { dg-prune-output "invalid combination of multiple type-specifiers" }
diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
index e0e40309a6d..0b105e0df1b 100644
--- a/libstdc++-v3/include/bits/utility.h
+++ b/libstdc++-v3/include/bits/utility.h
@@ -224,6 +224,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif // C++17
 #endif // C++14
 
+#if __has_builtin(__builtin_type_pack_element) // GCC
+
+  template<size_t _Np, typename... _Types>
+    struct _Nth_type
+    { using type = __builtin_type_pack_element(_Np, _Types...); };
+
+  // FIXME: use built-in directly, but requires mangling for the built-in.
+  template<size_t _Np, typename... _Types>
+    using _Nth_type_t = typename _Nth_type<_Np, _Types...>::type;
+
+#elif __has_builtin(__type_pack_element) // Clang
+
+  template<size_t _Np, typename... _Types>
+    struct _Nth_type
+    { using type = __type_pack_element<_Np, _Types...>; };
+
+  // Defined this way to keep the mangling compatible.
+  template<size_t _Np, typename... _Types>
+    using _Nth_type_t = typename _Nth_type<_Np, _Types...>::type;
+
+#else // Pure C++ fallback
+
   template<size_t _Np, typename... _Types>
     struct _Nth_type
     { };
@@ -263,6 +285,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     { using type = _Tp1; };
 #endif
 
+  template<size_t _Np, typename... _Types>
+    using _Nth_type_t = typename _Nth_type<_Np, _Types...>::type;
+#endif // __has_builtin(__builtin_type_pack_element)
+
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace
 
diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index 6d0060a191c..8ff1ab42d20 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -1356,7 +1356,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     {
       static_assert(__i < sizeof...(_Types), "tuple index must be in range");
 
-      using type = typename _Nth_type<__i, _Types...>::type;
+      using type = _Nth_type_t<__i, _Types...>;
     };
 
   template<size_t __i, typename _Head, typename... _Tail>
diff --git a/libstdc++-v3/include/std/variant b/libstdc++-v3/include/std/variant
index 5ff1e3edcdf..5cfdaf31e24 100644
--- a/libstdc++-v3/include/std/variant
+++ b/libstdc++-v3/include/std/variant
@@ -98,7 +98,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     {
       static_assert(_Np < sizeof...(_Types));
 
-      using type = typename _Nth_type<_Np, _Types...>::type;
+      using type = _Nth_type_t<_Np, _Types...>;
     };
 
   template<size_t _Np, typename _Variant>
@@ -324,7 +324,7 @@ namespace __variant
     struct _Traits
     {
       static constexpr bool _S_default_ctor =
-	  is_default_constructible_v<typename _Nth_type<0, _Types...>::type>;
+	  is_default_constructible_v<_Nth_type_t<0, _Types...>>;
       static constexpr bool _S_copy_ctor =
 	  (is_copy_constructible_v<_Types> && ...);
       static constexpr bool _S_move_ctor =
@@ -352,8 +352,7 @@ namespace __variant
       // The following nothrow traits are for non-trivial SMFs. Trivial SMFs
       // are always nothrow.
       static constexpr bool _S_nothrow_default_ctor =
-	  is_nothrow_default_constructible_v<
-	      typename _Nth_type<0, _Types...>::type>;
+	  is_nothrow_default_constructible_v<_Nth_type_t<0, _Types...>>;
       static constexpr bool _S_nothrow_copy_ctor = false;
       static constexpr bool _S_nothrow_move_ctor =
 	  (is_nothrow_move_constructible_v<_Types> && ...);
@@ -645,7 +644,7 @@ namespace __variant
 	      __variant::__get<__j>(*this) = __rhs_mem;
 	    else
 	      {
-		using _Tj = typename _Nth_type<__j, _Types...>::type;
+		using _Tj = _Nth_type_t<__j, _Types...>;
 		if constexpr (is_nothrow_copy_constructible_v<_Tj>
 			      || !is_nothrow_move_constructible_v<_Tj>)
 		  __variant::__emplace<__j>(*this, __rhs_mem);
@@ -697,7 +696,7 @@ namespace __variant
 		  __variant::__get<__j>(*this) = std::move(__rhs_mem);
 		else
 		  {
-		    using _Tj = typename _Nth_type<__j, _Types...>::type;
+		    using _Tj = _Nth_type_t<__j, _Types...>;
 		    if constexpr (is_nothrow_move_constructible_v<_Tj>)
 		      __variant::__emplace<__j>(*this, std::move(__rhs_mem));
 		    else
@@ -870,7 +869,7 @@ namespace __variant
       static constexpr size_t __index =
 	sizeof...(_Variants) - sizeof...(__rest) - 1;
 
-      using _Variant = typename _Nth_type<__index, _Variants...>::type;
+      using _Variant = _Nth_type_t<__index, _Variants...>;
 
       static constexpr int __do_cookie =
 	__extra_visit_slot_needed<_Ret, _Variant> ? 1 : 0;
@@ -932,8 +931,7 @@ namespace __variant
 	std::index_sequence<__indices...>>
     {
       using _Next =
-	  remove_reference_t<typename _Nth_type<sizeof...(__indices),
-			     _Variants...>::type>;
+	  remove_reference_t<_Nth_type_t<sizeof...(__indices), _Variants...>>;
       using _Array_type =
 	  _Multi_array<_Result_type (*)(_Visitor, _Variants...),
 		       __dimensions...>;
@@ -1374,7 +1372,7 @@ namespace __variant
 	  = __detail::__variant::__accepted_index<_Tp, variant>;
 
       template<size_t _Np, typename = enable_if_t<(_Np < sizeof...(_Types))>>
-	using __to_type = typename _Nth_type<_Np, _Types...>::type;
+	using __to_type = _Nth_type_t<_Np, _Types...>;
 
       template<typename _Tp, typename = enable_if_t<__not_self<_Tp>>>
 	using __accepted_type = __to_type<__accepted_index<_Tp>>;
@@ -1511,7 +1509,7 @@ namespace __variant
 	emplace(_Args&&... __args)
 	{
 	  namespace __variant = std::__detail::__variant;
-	  using type = typename _Nth_type<_Np, _Types...>::type;
+	  using type = _Nth_type_t<_Np, _Types...>;
 	  // Provide the strong exception-safety guarantee when possible,
 	  // to avoid becoming valueless.
 	  if constexpr (is_nothrow_constructible_v<type, _Args...>)
@@ -1551,7 +1549,7 @@ namespace __variant
 	emplace(initializer_list<_Up> __il, _Args&&... __args)
 	{
 	  namespace __variant = std::__detail::__variant;
-	  using type = typename _Nth_type<_Np, _Types...>::type;
+	  using type = _Nth_type_t<_Np, _Types...>;
 	  // Provide the strong exception-safety guarantee when possible,
 	  // to avoid becoming valueless.
 	  if constexpr (is_nothrow_constructible_v<type,
@@ -1734,7 +1732,7 @@ namespace __variant
 	  constexpr size_t __max = 11; // "These go to eleven."
 
 	  // The type of the first variant in the pack.
-	  using _V0 = typename _Nth_type<0, _Variants...>::type;
+	  using _V0 = _Nth_type_t<0, _Variants...>;
 	  // The number of alternatives in that first variant.
 	  constexpr auto __n = variant_size_v<remove_reference_t<_V0>>;
 
diff --git a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
index 84e085ebfbf..6279e24ba79 100644
--- a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
@@ -61,3 +61,4 @@ test03()
 
 // { dg-error "tuple index must be in range" "" { target *-*-* } 0 }
 // { dg-prune-output "no type named 'type' in .*_Nth_type" }
+// { dg-prune-output "'__builtin_type_pack_element' index is out of range" }
-- 
2.36.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2022-07-07 17:14 [PATCH] c++: Define built-in for std::tuple_element [PR100157] Jonathan Wakely
@ 2022-07-07 17:25 ` Marek Polacek
  2022-07-07 19:28 ` Jason Merrill
  2022-10-05 13:43 ` Patrick Palka
  2 siblings, 0 replies; 14+ messages in thread
From: Marek Polacek @ 2022-07-07 17:25 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: libstdc++, gcc-patches, Jason Merrill

On Thu, Jul 07, 2022 at 06:14:36PM +0100, Jonathan Wakely wrote:
> This adds a new built-in to replace the recursive class template
> instantiations done by traits such as std::tuple_element and
> std::variant_alternative. The purpose is to select the Nth type from a
> list of types, e.g. __builtin_type_pack_element(1, char, int, float) is
> int.
> 
> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> the compilation time is reduced by more than 90% and the memory  used by
> the compiler is reduced by 97%. In realistic examples the gains will be
> much smaller, but still relevant.
> 
> Clang has a similar built-in, __type_pack_element<N, T...>, but that's a
> "magic template" built-in using <> syntax, which GCC doesn't support. So
> this provides an equivalent feature, but as a built-in function using
> parens instead of <>. I don't really like the name "type pack element"
> (it gives you an element from a pack of types) but the semi-consistency
> with Clang seems like a reasonable argument in favour of keeping the
> name. I'd be open to alternative names though, e.g. __builtin_nth_type
> or __builtin_type_at_index.
> 
> 
> The patch has some problems though ...
> 
> FIXME 1: Marek pointed out that this this ICEs:
> template<class... T> using type = __builtin_type_pack_element(sizeof(T), T...);
> type<int, char> c;
> 
> The sizeof(T) expression is invalid, because T is an unexpanded pack,
> but it's not rejected and instead crashes:

I think this could be fixed by

  if (check_for_bare_parameter_packs (n))
    return error_mark_node;

in finish_type_pack_element.

(I haven't looked at the rest of the patch yet.)

Marek


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2022-07-07 17:14 [PATCH] c++: Define built-in for std::tuple_element [PR100157] Jonathan Wakely
  2022-07-07 17:25 ` Marek Polacek
@ 2022-07-07 19:28 ` Jason Merrill
  2022-07-07 20:46   ` Jonathan Wakely
  2022-10-05 13:43 ` Patrick Palka
  2 siblings, 1 reply; 14+ messages in thread
From: Jason Merrill @ 2022-07-07 19:28 UTC (permalink / raw)
  To: Jonathan Wakely, libstdc++, gcc-patches; +Cc: Marek Polacek

On 7/7/22 13:14, Jonathan Wakely wrote:
> This adds a new built-in to replace the recursive class template
> instantiations done by traits such as std::tuple_element and
> std::variant_alternative. The purpose is to select the Nth type from a
> list of types, e.g. __builtin_type_pack_element(1, char, int, float) is
> int.
> 
> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> the compilation time is reduced by more than 90% and the memory  used by
> the compiler is reduced by 97%. In realistic examples the gains will be
> much smaller, but still relevant.
> 
> Clang has a similar built-in, __type_pack_element<N, T...>, but that's a
> "magic template" built-in using <> syntax, which GCC doesn't support. So
> this provides an equivalent feature, but as a built-in function using
> parens instead of <>. I don't really like the name "type pack element"
> (it gives you an element from a pack of types) but the semi-consistency
> with Clang seems like a reasonable argument in favour of keeping the
> name. I'd be open to alternative names though, e.g. __builtin_nth_type
> or __builtin_type_at_index.
> 
> 
> The patch has some problems though ...
> 
> FIXME 1: Marek pointed out that this this ICEs:
> template<class... T> using type = __builtin_type_pack_element(sizeof(T), T...);
> type<int, char> c;
> 
> The sizeof(T) expression is invalid, because T is an unexpanded pack,
> but it's not rejected and instead crashes:
> 
> ice.C: In substitution of 'template<class ... T> using type = __builtin_type_pack_element (sizeof (T), T ...) [with T = {int, char}]':
> ice.C:2:15:   required from here
> ice.C:1:63: internal compiler error: in dependent_type_p, at cp/pt.cc:27490
>      1 | template<class... T> using type = __builtin_type_pack_element(sizeof(T), T...);
>        |                                                               ^~~~~~~~~
> 0xe13eea dependent_type_p(tree_node*)
>          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:27490
> 0xeb1286 cxx_sizeof_or_alignof_type(unsigned int, tree_node*, tree_code, bool, bool)
>          /home/jwakely/src/gcc/gcc/gcc/cp/typeck.cc:1912
> 0xdf4fcc tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
>          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:20582
> 0xdd9121 tsubst_tree_list(tree_node*, tree_node*, int, tree_node*)
>          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15587
> 0xddb583 tsubst(tree_node*, tree_node*, int, tree_node*)
>          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:16056
> 0xddcc9d tsubst(tree_node*, tree_node*, int, tree_node*)
>          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:16436
> 0xdd6d45 tsubst_decl
>          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15038
> 0xdd952a tsubst(tree_node*, tree_node*, int, tree_node*)
>          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15668
> 0xdfb9a1 instantiate_template(tree_node*, tree_node*, int)
>          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:21811
> 0xdfc1b6 instantiate_alias_template
>          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:21896
> 0xdd9796 tsubst(tree_node*, tree_node*, int, tree_node*)
>          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15696
> 0xdbaba5 lookup_template_class(tree_node*, tree_node*, tree_node*, tree_node*, int, int)
>          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:10131
> 0xe4bac0 finish_template_type(tree_node*, tree_node*, int)
>          /home/jwakely/src/gcc/gcc/gcc/cp/semantics.cc:3727
> 0xd334c8 cp_parser_template_id
>          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:18458
> 0xd429b0 cp_parser_class_name
>          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:25923
> 0xd1ade9 cp_parser_qualifying_entity
>          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:7193
> 0xd1a2c8 cp_parser_nested_name_specifier_opt
>          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:6875
> 0xd4eefd cp_parser_template_introduction
>          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:31668
> 0xd4f416 cp_parser_template_declaration_after_export
>          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:31840
> 0xd2d60e cp_parser_declaration
>          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:15083
> 
> 
> FIXME 2: I want to mangle __builtin_type_pack_element(N, T...) the same as
> typename std::_Nth_type<N, T...>::type but I don't know how. Instead of
> trying to fake the mangled string, it's probably better to build a decl
> for that nested type, right? Any suggestions where to find something
> similar I can learn from?

The tricky thing is dealing with mangling compression, where we use a 
substitution instead of repeating a type; that's definitely easier if we 
actually have the type.

So you'd probably want to have a declaration of std::_Nth_type to work 
with, and lookup_template_class to get the type of that specialization. 
  And then if it's complete, look up ...::type; if not, we could 
probably stuff a ...::type in its TYPE_FIELDS that would get clobbered 
if we actually instantiated the type...

> The reason to mangle it that way is that it preserves the same symbol
> names as the library produced in GCC 12, and that it will still produce
> with non-GCC compilers (see the definitions of std::_Nth_type in the
> library parts of the patch). If we don't do that then either we need to
> ensure it never appears in a mangled name, or define some other
> GCC-specific mangling for this built-in (e.g. we could just mangle it as
> though it's a function, "19__builtin_type_pack_elementELm1EJDpT_E" or
> something like that!). If we ensure it doesn't appear in mangled names
> that means we still need to instantiate the _Nth_type class template,
> rather than defining the alias template _Nth_type_t to use the built-in
> directly.  That loses a little of the compilation performance gain that
> comes from defining the built-in in the first place (although avoid the
> recusrion is the biggest gain, which we'd still get).

...but this seems a lot simpler.

> FIXME 3:This change regresses diagnostics from std::tuple_element
> because as well as the existing:
> 
> tuple:1357: error: static assertion failed: tuple index must be in range
> 
> we now also get:
> 
> bits/utility.h:231: error: '__builtin_type_pack_element' index is out of range
> 
> which is not very user-friendly.
> 
> Maybe that should not mention the built-in by name, and just say
> something generic like "out of range index into type pack". Or maybe
> diagnose it as std::_Nth_type_t, like I plan to mangle it. That would be
> a bit confusing for anybody using the built-in directly, but I'm sure
> they can live with it - they're already using a non-portable intrinsic.
> 
> That still means two errors where we used to only print one though.
> Maybe the library needs to try harder to not let invalid indices reach
> the built-in. Currently we have:
> 
>    template<size_t __i, typename... _Types>
>      struct tuple_element<__i, tuple<_Types...>>
>      {
>        static_assert(__i < sizeof...(_Types), "tuple index must be in range");
> 
>        using type = _Nth_type_t<__i, _Types...>;
>      };
> 
> And _Nth_type_t is an alias for a type defined using the built-in.
> 
> Maybe I need to add another level of indirection (losing some of the
> compile-time improvements that the built-in is supposed to provide) or
> use concepts to constrain _Nth_type_t (which doesn't help pre-C++20).
> 
> This seems like another case where the compiler should just stop when a
> static assert fails. The whole point of that assertion is that what
> follows doesn't make sense unless it passes.

I spent a while pursuing that last year, but found that often stopping 
leads to more error cascade than continuing.  Perhaps we could continue 
but mute errors...
> Suggestions for how to fix these issues are welcome, I think I've gone
> as far as I can for now.
> 
> 
> -- >8 --
> 
> Add __builtin_type_pack_element so that std::tuple_element and
> std::variant_alternative can be implemented efficiently, without
> recursive class template instantiations.
> 
> The name is intended to be similar to Clang's __type_pack_element<>
> built-in which has the same behaviour, but uses template syntax instead
> of function-call syntax.
> 
> The libstdc++ headers can be updated to use this new built-in, or
> Clang's equivalent. Until the FIXME in mangle.cc:write_type is fixed
> (i.e. mangling for the built-in is added) we can't defined the
> _Nth_type_t alias template to use the built-in directly, because
> function templates that use the alias would need to mangle the built-in.
> I suggest mangling it as typename _Nth_type<N, T...>::type which will be
> backwards compatible with symbol names from GCC 12 and Clang.
> 
> 	PR c++/100157
> 
> gcc/c-family/ChangeLog:
> 
> 	* c-common.cc (c_common_reswords): Add
> 	__builtin_type_pack_element.
> 	* c-common.h (enum rid): Add RID_TYPE_PACK_ELEMENT.
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (diagnose_trait_expr): Add
> 	CPTK_TYPE_PACK_ELEMENT to switch.
> 	* cp-objcp-common.cc (names_builtin_p): Add
> 	RID_TYPE_PACK_ELEMENT to switch.
> 	(cp_common_init_ts): Mark TYPE_PACK_ELEMENT as not having a
> 	common member.
> 	* cp-tree.def (TYPE_PACK_ELEMENT): Define tree code.
> 	* cp-tree.h (enum cp_trait_kind): Add CPTK_TYPE_PACK_ELEMENT.
> 	(TYPE_PACK_ELEMENT_ARGS): Define.
> 	(finish_type_pack_element): Declare.
> 	* error.cc (dump_type): Add TYPE_PACK_ELEMENT to switch.
> 	(dump_type_prefix): Likewise.
> 	(dump_type_suffix): Likewise.
> 	* mangle.cc (write_type): Likewise.
> 	* parser.cc (cp_keyword_starts_decl_specifier_p): Add
> 	RID_TYPE_PACK_ELEMENT to switch.
> 	(cp_parser_trait_expr): Likewise. Parse its arguments and call
> 	finish_type_pack_element.
> 	(cp_parser_simple_type_specifier): Add RID_TYPE_PACK_ELEMENT to
> 	switch.
> 	* pt.cc (for_each_template_parm_r): Add TYPE_PACK_ELEMENT to
> 	switch.
> 	(tsubst): Likewise.
> 	(unify): Likewise.
> 	(dependent_type_p_r): A TYPE_PACK_ELEMENT is dependent.
> 	* semantics.cc (finish_type_pack_element): New function.
> 
> gcc/ChangeLog:
> 
> 	* doc/extend.texi (Type Traits): Document new built-in.
> 
> libstdc++-v3/ChangeLog:
> 
> 	* include/bits/utility.h (_Nth_type_t): New alias template using
> 	built-ins when available.
> 	* include/std/tuple: Use _Nth_type_t instead of _Nth_type.
> 	* include/std/variant: Likewise.
> 	* testsuite/20_util/tuple/element_access/get_neg.cc: Prune
> 	additional errors from the new built-in.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/ext/type_pack_element1.C: New test.
> 	* g++.dg/ext/type_pack_element2.C: New test.
> ---
>   gcc/c-family/c-common.cc                      |  1 +
>   gcc/c-family/c-common.h                       |  1 +
>   gcc/cp/constraint.cc                          |  1 +
>   gcc/cp/cp-objcp-common.cc                     |  2 +
>   gcc/cp/cp-tree.def                            |  4 ++
>   gcc/cp/cp-tree.h                              |  6 +++
>   gcc/cp/error.cc                               | 16 +++++++
>   gcc/cp/mangle.cc                              |  5 +++
>   gcc/cp/parser.cc                              | 39 ++++++++++++++--
>   gcc/cp/pt.cc                                  | 18 ++++++--
>   gcc/cp/semantics.cc                           | 44 +++++++++++++++++++
>   gcc/doc/extend.texi                           |  6 +++
>   gcc/testsuite/g++.dg/ext/type_pack_element1.C | 26 +++++++++++
>   gcc/testsuite/g++.dg/ext/type_pack_element2.C | 31 +++++++++++++
>   libstdc++-v3/include/bits/utility.h           | 26 +++++++++++
>   libstdc++-v3/include/std/tuple                |  2 +-
>   libstdc++-v3/include/std/variant              | 24 +++++-----
>   .../20_util/tuple/element_access/get_neg.cc   |  1 +
>   18 files changed, 232 insertions(+), 21 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element1.C
>   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element2.C
> 
> diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> index 1b8e73f7bc5..655b571a4ee 100644
> --- a/gcc/c-family/c-common.cc
> +++ b/gcc/c-family/c-common.cc
> @@ -389,6 +389,7 @@ const struct c_common_resword c_common_reswords[] =
>     { "__builtin_shufflevector", RID_BUILTIN_SHUFFLEVECTOR, 0 },
>     { "__builtin_tgmath", RID_BUILTIN_TGMATH, D_CONLY },
>     { "__builtin_offsetof", RID_OFFSETOF, 0 },
> +  { "__builtin_type_pack_element", RID_TYPE_PACK_ELEMENT, D_CXXONLY },
>     { "__builtin_types_compatible_p", RID_TYPES_COMPATIBLE_P, D_CONLY },
>     { "__builtin_va_arg",	RID_VA_ARG,	0 },
>     { "__complex",	RID_COMPLEX,	0 },
> diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> index c0900848965..e9d0864f2ba 100644
> --- a/gcc/c-family/c-common.h
> +++ b/gcc/c-family/c-common.h
> @@ -184,6 +184,7 @@ enum rid
>     RID_IS_UNION,                RID_UNDERLYING_TYPE,
>     RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
>     RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
> +  RID_TYPE_PACK_ELEMENT,
>   
>     /* C++11 */
>     RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, RID_STATIC_ASSERT,
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 591155cee22..f7bd189ae47 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -3690,6 +3690,7 @@ diagnose_trait_expr (tree expr, tree args)
>       case CPTK_BASES:
>       case CPTK_DIRECT_BASES:
>       case CPTK_UNDERLYING_TYPE:
> +    case CPTK_TYPE_PACK_ELEMENT:
>         /* We shouldn't see these non-expression traits.  */
>         gcc_unreachable ();
>       /* We deliberately omit the default case so that when adding a new
> diff --git a/gcc/cp/cp-objcp-common.cc b/gcc/cp/cp-objcp-common.cc
> index 0b70d5567e4..bac6b5a6214 100644
> --- a/gcc/cp/cp-objcp-common.cc
> +++ b/gcc/cp/cp-objcp-common.cc
> @@ -461,6 +461,7 @@ names_builtin_p (const char *name)
>       case RID_IS_ASSIGNABLE:
>       case RID_IS_CONSTRUCTIBLE:
>       case RID_UNDERLYING_TYPE:
> +    case RID_TYPE_PACK_ELEMENT:
>         return true;
>       default:
>         break;
> @@ -517,6 +518,7 @@ cp_common_init_ts (void)
>     MARK_TS_TYPE_NON_COMMON (TEMPLATE_TEMPLATE_PARM);
>     MARK_TS_TYPE_NON_COMMON (TEMPLATE_TYPE_PARM);
>     MARK_TS_TYPE_NON_COMMON (TYPE_PACK_EXPANSION);
> +  MARK_TS_TYPE_NON_COMMON (TYPE_PACK_ELEMENT);
>   
>     /* Statements.  */
>     MARK_TS_EXP (CLEANUP_STMT);
> diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def
> index f9cbd339f19..6ea197f16af 100644
> --- a/gcc/cp/cp-tree.def
> +++ b/gcc/cp/cp-tree.def
> @@ -470,6 +470,10 @@ DEFTREECODE (DECLTYPE_TYPE, "decltype_type", tcc_type, 0)
>      UNDERLYING_TYPE_TYPE is the type in question.  */
>   DEFTREECODE (UNDERLYING_TYPE, "underlying_type", tcc_type, 0)
>   
> +/* A type designated by `__builtin_type_pack_element (n, type)'.
> +   TYPE_PACK_ELEMENT_ARGS contains the arguments.  */
> +DEFTREECODE (TYPE_PACK_ELEMENT, "builtin_type_pack_element", tcc_type, 0)
> +
>   /* A type designated by one of the bases type traits.
>      BASES_TYPE is the type in question.  */
>   DEFTREECODE (BASES, "bases", tcc_type, 0)
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 2fde4f83b41..2430b0b94c1 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -1393,6 +1393,7 @@ enum cp_trait_kind
>     CPTK_IS_TRIVIALLY_CONSTRUCTIBLE,
>     CPTK_IS_TRIVIALLY_COPYABLE,
>     CPTK_IS_UNION,
> +  CPTK_TYPE_PACK_ELEMENT,
>     CPTK_UNDERLYING_TYPE,
>     CPTK_IS_ASSIGNABLE,
>     CPTK_IS_CONSTRUCTIBLE,
> @@ -4789,6 +4790,10 @@ get_vec_init_expr (tree t)
>   #define UNDERLYING_TYPE_TYPE(NODE) \
>     (TYPE_VALUES_RAW (UNDERLYING_TYPE_CHECK (NODE)))
>   
> +/* The arguments for a TYPE_PACK_ELEMENT.  */
> +#define TYPE_PACK_ELEMENT_ARGS(NODE) \
> +  (TYPE_VALUES_RAW (TYPE_PACK_ELEMENT_CHECK (NODE)))
> +
>   /* The type in question for BASES.  */
>   #define BASES_TYPE(NODE) \
>     (TYPE_VALUES_RAW (BASES_CHECK (NODE)))
> @@ -7640,6 +7645,7 @@ extern cp_expr finish_id_expression		(tree, tree, tree,
>                                                    location_t);
>   extern tree finish_typeof			(tree);
>   extern tree finish_underlying_type	        (tree);
> +extern tree finish_type_pack_element	        (tree, tree);
>   extern tree calculate_bases                     (tree, tsubst_flags_t);
>   extern tree finish_bases                        (tree, bool);
>   extern tree calculate_direct_bases              (tree, tsubst_flags_t);
> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> index 94181e76d0e..1b6f4df51b9 100644
> --- a/gcc/cp/error.cc
> +++ b/gcc/cp/error.cc
> @@ -706,6 +706,20 @@ dump_type (cxx_pretty_printer *pp, tree t, int flags)
>         pp_cxx_right_paren (pp);
>         break;
>   
> +    case TYPE_PACK_ELEMENT:
> +      t = TYPE_PACK_ELEMENT_ARGS (t);
> +      pp_cxx_ws_string (pp, "__builtin_type_pack_element");
> +      pp_cxx_whitespace (pp);
> +      pp_cxx_left_paren (pp);
> +      dump_expr (pp, TREE_VALUE (t), flags & ~TFF_EXPR_IN_PARENS);
> +      for (tree arg = TREE_CHAIN (t); arg; arg = TREE_CHAIN (arg))
> +	{
> +	  pp_separate_with_comma (pp);
> +	  dump_type (pp, TREE_VALUE (arg), flags);
> +	}
> +      pp_cxx_right_paren (pp);
> +      break;
> +
>       case TYPE_PACK_EXPANSION:
>         dump_type (pp, PACK_EXPANSION_PATTERN (t), flags);
>         pp_cxx_ws_string (pp, "...");
> @@ -974,6 +988,7 @@ dump_type_prefix (cxx_pretty_printer *pp, tree t, int flags)
>       case UNDERLYING_TYPE:
>       case DECLTYPE_TYPE:
>       case TYPE_PACK_EXPANSION:
> +    case TYPE_PACK_ELEMENT:
>       case FIXED_POINT_TYPE:
>       case NULLPTR_TYPE:
>         dump_type (pp, t, flags);
> @@ -1098,6 +1113,7 @@ dump_type_suffix (cxx_pretty_printer *pp, tree t, int flags)
>       case UNDERLYING_TYPE:
>       case DECLTYPE_TYPE:
>       case TYPE_PACK_EXPANSION:
> +    case TYPE_PACK_ELEMENT:
>       case FIXED_POINT_TYPE:
>       case NULLPTR_TYPE:
>         break;
> diff --git a/gcc/cp/mangle.cc b/gcc/cp/mangle.cc
> index 75388e99bfd..fbaed426940 100644
> --- a/gcc/cp/mangle.cc
> +++ b/gcc/cp/mangle.cc
> @@ -2393,6 +2393,11 @@ write_type (tree type)
>   	      sorry ("mangling %<__underlying_type%>");
>   	      break;
>   
> +	    case TYPE_PACK_ELEMENT:
> +	      /* FIXME: Mangle as std::_Nth_type<N, T...>::type.  */
> +	      sorry ("mangling %<__builtin_type_pack_element%>");
> +	      break;
> +
>   	    case LANG_TYPE:
>   	      /* fall through.  */
>   
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index bf9ea3685f8..8f0f0331d4f 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -1142,6 +1142,7 @@ cp_keyword_starts_decl_specifier_p (enum rid keyword)
>         /* C++11 extensions.  */
>       case RID_DECLTYPE:
>       case RID_UNDERLYING_TYPE:
> +    case RID_TYPE_PACK_ELEMENT:
>       case RID_CONSTEXPR:
>         /* C++20 extensions.  */
>       case RID_CONSTINIT:
> @@ -10966,6 +10967,10 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
>       case RID_UNDERLYING_TYPE:
>         kind = CPTK_UNDERLYING_TYPE;
>         break;
> +    case RID_TYPE_PACK_ELEMENT:
> +      kind = CPTK_TYPE_PACK_ELEMENT;
> +      variadic = true;
> +      break;
>       case RID_BASES:
>         kind = CPTK_BASES;
>         break;
> @@ -11001,10 +11006,24 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
>     matching_parens parens;
>     parens.require_open (parser);
>   
> -  {
> -    type_id_in_expr_sentinel s (parser);
> -    type1 = cp_parser_type_id (parser);
> -  }
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    {
> +      cp_expr e = cp_parser_constant_expression (parser, 0, nullptr, true);
> +      if (!cp_lexer_next_token_is (parser->lexer, CPP_COMMA))
> +	{
> +	  location_t err_loc = cp_lexer_peek_token (parser->lexer)->location;
> +	  err_loc = make_location (err_loc, start_loc, err_loc);
> +	  error_at (err_loc, "%<__builtin_type_pack_element%> requires"
> +			      " one or more type arguments");
> +	  return error_mark_node;
> +	}
> +      type1 = e.get_value (); // actually a constant-expression, not a type
> +    }
> +  else
> +    {
> +      type_id_in_expr_sentinel s (parser);
> +      type1 = cp_parser_type_id (parser);
> +    }
>   
>     if (type1 == error_mark_node)
>       return error_mark_node;
> @@ -11054,6 +11073,8 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
>       {
>       case CPTK_UNDERLYING_TYPE:
>         return cp_expr (finish_underlying_type (type1), trait_loc);
> +    case CPTK_TYPE_PACK_ELEMENT:
> +      return cp_expr (finish_type_pack_element (type1, type2), trait_loc);
>       case CPTK_BASES:
>         return cp_expr (finish_bases (type1, false), trait_loc);
>       case CPTK_DIRECT_BASES:
> @@ -19571,6 +19592,7 @@ cp_parser_type_specifier (cp_parser* parser,
>        char16_t
>        char32_t
>        __underlying_type ( type-id )
> +     __builtin_type_pack_element ( constant-expression , type-id , [opt] )
>   
>      C++17 extension:
>   
> @@ -19783,6 +19805,15 @@ cp_parser_simple_type_specifier (cp_parser* parser,
>   
>         return type;
>   
> +    case RID_TYPE_PACK_ELEMENT:
> +      type = cp_parser_trait_expr (parser, RID_TYPE_PACK_ELEMENT);
> +      if (decl_specs)
> +	cp_parser_set_decl_spec_type (decl_specs, type,
> +				      token,
> +				      /*type_definition_p=*/false);
> +
> +      return type;
> +
>       case RID_BASES:
>       case RID_DIRECT_BASES:
>         type = cp_parser_trait_expr (parser, token->keyword);
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 8672da123f4..590b3eccc84 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -10592,6 +10592,7 @@ for_each_template_parm_r (tree *tp, int *walk_subtrees, void *d)
>       case TYPEOF_TYPE:
>       case DECLTYPE_TYPE:
>       case UNDERLYING_TYPE:
> +    case TYPE_PACK_ELEMENT:
>         if (pfd->include_nondeduced_p
>   	  && for_each_template_parm (TYPE_VALUES_RAW (t), fn, data,
>   				     pfd->visited,
> @@ -16430,6 +16431,15 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>   	return finish_underlying_type (type);
>         }
>   
> +    case TYPE_PACK_ELEMENT:
> +      {
> +	tree subst_args = tsubst (TYPE_PACK_ELEMENT_ARGS (t), args,
> +				  complain, in_decl);
> +	tree pack_index = TREE_VALUE (subst_args);
> +	tree types = TREE_CHAIN (subst_args);
> +	return finish_type_pack_element (pack_index, types);
> +      }
> +
>       case TYPE_ARGUMENT_PACK:
>       case NONTYPE_ARGUMENT_PACK:
>         return tsubst_argument_pack (t, args, complain, in_decl);
> @@ -24829,8 +24839,9 @@ unify (tree tparms, tree targs, tree parm, tree arg, int strict,
>       case TYPEOF_TYPE:
>       case DECLTYPE_TYPE:
>       case UNDERLYING_TYPE:
> +    case TYPE_PACK_ELEMENT:
>         /* Cannot deduce anything from TYPEOF_TYPE, DECLTYPE_TYPE,
> -	 or UNDERLYING_TYPE nodes.  */
> +	 UNDERLYING_TYPE, or TYPE_PACK_ELEMENT nodes.  */
>         return unify_success (explain_p);
>   
>       case ERROR_MARK:
> @@ -27405,12 +27416,13 @@ dependent_type_p_r (tree type)
>   	       (INNERMOST_TEMPLATE_ARGS (CLASSTYPE_TI_ARGS (type)))))
>       return true;
>   
> -  /* All TYPEOF_TYPEs, DECLTYPE_TYPEs, and UNDERLYING_TYPEs are
> +  /* All these are
>        dependent; if the argument of the `typeof' expression is not
>        type-dependent, then it should already been have resolved.  */
>     if (TREE_CODE (type) == TYPEOF_TYPE
>         || TREE_CODE (type) == DECLTYPE_TYPE
> -      || TREE_CODE (type) == UNDERLYING_TYPE)
> +      || TREE_CODE (type) == UNDERLYING_TYPE
> +      || TREE_CODE (type) == TYPE_PACK_ELEMENT)
>       return true;
>   
>     /* A template argument pack is dependent if any of its packed
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index 2344b5eea00..36eff75ed45 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -4418,6 +4418,50 @@ finish_underlying_type (tree type)
>     return underlying_type;
>   }
>   
> +/* Implement the __builtin_type_pack_element keyword: Return the type
> +   at index N in TYPES..., suitable for use as a type-specifier.  */
> +
> +tree
> +finish_type_pack_element (tree n, tree types)
> +{
> +  if (n == error_mark_node
> +      || types == error_mark_node)
> +    return error_mark_node;
> +
> +  if (processing_template_decl)
> +    {
> +      if (value_dependent_expression_p (n) || uses_template_parms (types))
> +	{
> +	  tree t = cxx_make_type (TYPE_PACK_ELEMENT);
> +	  TYPE_PACK_ELEMENT_ARGS (t) = tree_cons (NULL_TREE, n, types);
> +	  return t;
> +	}
> +    }
> +
> +  n = fold_non_dependent_expr (n);
> +
> +  if (!INTEGRAL_TYPE_P (TREE_TYPE (n)) || TREE_CODE (n) != INTEGER_CST)
> +    {
> +      error ("%<__builtin_type_pack_element%> index is not an integral"
> +	     " constant");
> +      return error_mark_node;
> +    }
> +
> +  HOST_WIDE_INT lunroll = tree_to_shwi (n);
> +  if (lunroll < 0 || lunroll >= list_length (types))
> +    {
> +      error ("%<__builtin_type_pack_element%> index is out of range");
> +      return error_mark_node;
> +    }
> +
> +  unsigned index = (unsigned)lunroll;
> +  while (index-- > 0 && types != NULL_TREE)
> +    types = TREE_CHAIN (types);
> +  if (types == NULL_TREE)
> +    return error_mark_node;
> +  return TREE_VALUE (types);
> +}
> +
>   /* Implement the __direct_bases keyword: Return the direct base classes
>      of type.  */
>   
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index dfbe33ac652..5f0f39fe72e 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -25201,6 +25201,12 @@ definition, expands to a template argument pack containing integers
>   from @code{0} to @code{length-1}.  This is provided for efficient
>   implementation of @code{std::make_integer_sequence}.
>   
> +@item __builtin_type_pack_element (n, types...)
> +The Nth type in the list of types.
> +This is provided for efficient
> +implementation of @code{std::tuple_element} and similar.
> +Requires: 0 @leq{} @code{n} < number of type arguments.
> +
>   @end table
>   
>   
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element1.C b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> new file mode 100644
> index 00000000000..cc4b6b4b67f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> @@ -0,0 +1,26 @@
> +// { dg-do compile { target c++11 } }
> +
> +template<class, class> struct is_same { static constexpr bool value = false; };
> +template<class T> struct is_same<T,T> { static constexpr bool value = true; };
> +
> +static_assert( is_same<__builtin_type_pack_element(0, int), int>::value, "" );
> +static_assert( is_same<__builtin_type_pack_element(0, long), long>::value, "" );
> +static_assert( is_same<__builtin_type_pack_element(1, float, char, int), char>::value, "" );
> +
> +using T = __builtin_type_pack_element(sizeof('0'), int, int);
> +
> +template<int N, class... T>
> +using Nth_type = __builtin_type_pack_element(N, T...);
> +
> +static_assert( is_same<Nth_type<0, int>, int>::value, "" );
> +static_assert( is_same<Nth_type<0, long>, long>::value, "" );
> +static_assert( is_same<Nth_type<1, float, char, int>, char>::value, "" );
> +
> +template<int N>
> +struct Nth_type_class_template
> +{
> +  using type = __builtin_type_pack_element(N, int, void, char, float, long);
> +};
> +
> +static_assert( is_same<typename Nth_type_class_template<0>::type, int>::value, "" );
> +static_assert( is_same<typename Nth_type_class_template<1>::type, void>::value, "" );
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element2.C b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> new file mode 100644
> index 00000000000..04040e0e84f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> @@ -0,0 +1,31 @@
> +// { dg-do compile { target c++11 } }
> +// { dg-options -w }
> +
> +using X1 = __builtin_type_pack_element(1);  // { dg-error "one or more type" }
> +using X2 = __builtin_type_pack_element(1, 1); // { dg-error "expected type" }
> +int i;
> +using X3 = __builtin_type_pack_element(1, i); // { dg-error "does not name a type" }
> +using X4 = __builtin_type_pack_element(1, int); // { dg-error "out of range" }
> +
> +using X5 = __builtin_type_pack_element(-1, int); // { dg-error "out of range" }
> +using X6 = __builtin_type_pack_element(nullptr, int); // { dg-error "integral" }
> +
> +template<int N, class T>
> +struct uninstantiated_template
> +{
> +  using X = __builtin_type_pack_element(2, int); // { dg-error "out of range" }
> +  using Y = __builtin_type_pack_element(2, T);   // { dg-bogus "out of range" }
> +  using Z = __builtin_type_pack_element(N, int); // { dg-bogus "." }
> +};
> +
> +
> +template<int N, class T>
> +struct instantiated_template
> +{
> +  using Y = __builtin_type_pack_element(2, T); // { dg-error "out of range" }
> +  using Z = __builtin_type_pack_element(N, T); // { dg-error "out of range" }
> +};
> +
> +using Y = typename instantiated_template<0, int>::Y;
> +using Z = typename instantiated_template<1, int>::Z;
> +// { dg-prune-output "invalid combination of multiple type-specifiers" }
> diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
> index e0e40309a6d..0b105e0df1b 100644
> --- a/libstdc++-v3/include/bits/utility.h
> +++ b/libstdc++-v3/include/bits/utility.h
> @@ -224,6 +224,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>   #endif // C++17
>   #endif // C++14
>   
> +#if __has_builtin(__builtin_type_pack_element) // GCC
> +
> +  template<size_t _Np, typename... _Types>
> +    struct _Nth_type
> +    { using type = __builtin_type_pack_element(_Np, _Types...); };
> +
> +  // FIXME: use built-in directly, but requires mangling for the built-in.
> +  template<size_t _Np, typename... _Types>
> +    using _Nth_type_t = typename _Nth_type<_Np, _Types...>::type;
> +
> +#elif __has_builtin(__type_pack_element) // Clang
> +
> +  template<size_t _Np, typename... _Types>
> +    struct _Nth_type
> +    { using type = __type_pack_element<_Np, _Types...>; };
> +
> +  // Defined this way to keep the mangling compatible.
> +  template<size_t _Np, typename... _Types>
> +    using _Nth_type_t = typename _Nth_type<_Np, _Types...>::type;
> +
> +#else // Pure C++ fallback
> +
>     template<size_t _Np, typename... _Types>
>       struct _Nth_type
>       { };
> @@ -263,6 +285,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       { using type = _Tp1; };
>   #endif
>   
> +  template<size_t _Np, typename... _Types>
> +    using _Nth_type_t = typename _Nth_type<_Np, _Types...>::type;
> +#endif // __has_builtin(__builtin_type_pack_element)
> +
>   _GLIBCXX_END_NAMESPACE_VERSION
>   } // namespace
>   
> diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> index 6d0060a191c..8ff1ab42d20 100644
> --- a/libstdc++-v3/include/std/tuple
> +++ b/libstdc++-v3/include/std/tuple
> @@ -1356,7 +1356,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       {
>         static_assert(__i < sizeof...(_Types), "tuple index must be in range");
>   
> -      using type = typename _Nth_type<__i, _Types...>::type;
> +      using type = _Nth_type_t<__i, _Types...>;
>       };
>   
>     template<size_t __i, typename _Head, typename... _Tail>
> diff --git a/libstdc++-v3/include/std/variant b/libstdc++-v3/include/std/variant
> index 5ff1e3edcdf..5cfdaf31e24 100644
> --- a/libstdc++-v3/include/std/variant
> +++ b/libstdc++-v3/include/std/variant
> @@ -98,7 +98,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       {
>         static_assert(_Np < sizeof...(_Types));
>   
> -      using type = typename _Nth_type<_Np, _Types...>::type;
> +      using type = _Nth_type_t<_Np, _Types...>;
>       };
>   
>     template<size_t _Np, typename _Variant>
> @@ -324,7 +324,7 @@ namespace __variant
>       struct _Traits
>       {
>         static constexpr bool _S_default_ctor =
> -	  is_default_constructible_v<typename _Nth_type<0, _Types...>::type>;
> +	  is_default_constructible_v<_Nth_type_t<0, _Types...>>;
>         static constexpr bool _S_copy_ctor =
>   	  (is_copy_constructible_v<_Types> && ...);
>         static constexpr bool _S_move_ctor =
> @@ -352,8 +352,7 @@ namespace __variant
>         // The following nothrow traits are for non-trivial SMFs. Trivial SMFs
>         // are always nothrow.
>         static constexpr bool _S_nothrow_default_ctor =
> -	  is_nothrow_default_constructible_v<
> -	      typename _Nth_type<0, _Types...>::type>;
> +	  is_nothrow_default_constructible_v<_Nth_type_t<0, _Types...>>;
>         static constexpr bool _S_nothrow_copy_ctor = false;
>         static constexpr bool _S_nothrow_move_ctor =
>   	  (is_nothrow_move_constructible_v<_Types> && ...);
> @@ -645,7 +644,7 @@ namespace __variant
>   	      __variant::__get<__j>(*this) = __rhs_mem;
>   	    else
>   	      {
> -		using _Tj = typename _Nth_type<__j, _Types...>::type;
> +		using _Tj = _Nth_type_t<__j, _Types...>;
>   		if constexpr (is_nothrow_copy_constructible_v<_Tj>
>   			      || !is_nothrow_move_constructible_v<_Tj>)
>   		  __variant::__emplace<__j>(*this, __rhs_mem);
> @@ -697,7 +696,7 @@ namespace __variant
>   		  __variant::__get<__j>(*this) = std::move(__rhs_mem);
>   		else
>   		  {
> -		    using _Tj = typename _Nth_type<__j, _Types...>::type;
> +		    using _Tj = _Nth_type_t<__j, _Types...>;
>   		    if constexpr (is_nothrow_move_constructible_v<_Tj>)
>   		      __variant::__emplace<__j>(*this, std::move(__rhs_mem));
>   		    else
> @@ -870,7 +869,7 @@ namespace __variant
>         static constexpr size_t __index =
>   	sizeof...(_Variants) - sizeof...(__rest) - 1;
>   
> -      using _Variant = typename _Nth_type<__index, _Variants...>::type;
> +      using _Variant = _Nth_type_t<__index, _Variants...>;
>   
>         static constexpr int __do_cookie =
>   	__extra_visit_slot_needed<_Ret, _Variant> ? 1 : 0;
> @@ -932,8 +931,7 @@ namespace __variant
>   	std::index_sequence<__indices...>>
>       {
>         using _Next =
> -	  remove_reference_t<typename _Nth_type<sizeof...(__indices),
> -			     _Variants...>::type>;
> +	  remove_reference_t<_Nth_type_t<sizeof...(__indices), _Variants...>>;
>         using _Array_type =
>   	  _Multi_array<_Result_type (*)(_Visitor, _Variants...),
>   		       __dimensions...>;
> @@ -1374,7 +1372,7 @@ namespace __variant
>   	  = __detail::__variant::__accepted_index<_Tp, variant>;
>   
>         template<size_t _Np, typename = enable_if_t<(_Np < sizeof...(_Types))>>
> -	using __to_type = typename _Nth_type<_Np, _Types...>::type;
> +	using __to_type = _Nth_type_t<_Np, _Types...>;
>   
>         template<typename _Tp, typename = enable_if_t<__not_self<_Tp>>>
>   	using __accepted_type = __to_type<__accepted_index<_Tp>>;
> @@ -1511,7 +1509,7 @@ namespace __variant
>   	emplace(_Args&&... __args)
>   	{
>   	  namespace __variant = std::__detail::__variant;
> -	  using type = typename _Nth_type<_Np, _Types...>::type;
> +	  using type = _Nth_type_t<_Np, _Types...>;
>   	  // Provide the strong exception-safety guarantee when possible,
>   	  // to avoid becoming valueless.
>   	  if constexpr (is_nothrow_constructible_v<type, _Args...>)
> @@ -1551,7 +1549,7 @@ namespace __variant
>   	emplace(initializer_list<_Up> __il, _Args&&... __args)
>   	{
>   	  namespace __variant = std::__detail::__variant;
> -	  using type = typename _Nth_type<_Np, _Types...>::type;
> +	  using type = _Nth_type_t<_Np, _Types...>;
>   	  // Provide the strong exception-safety guarantee when possible,
>   	  // to avoid becoming valueless.
>   	  if constexpr (is_nothrow_constructible_v<type,
> @@ -1734,7 +1732,7 @@ namespace __variant
>   	  constexpr size_t __max = 11; // "These go to eleven."
>   
>   	  // The type of the first variant in the pack.
> -	  using _V0 = typename _Nth_type<0, _Variants...>::type;
> +	  using _V0 = _Nth_type_t<0, _Variants...>;
>   	  // The number of alternatives in that first variant.
>   	  constexpr auto __n = variant_size_v<remove_reference_t<_V0>>;
>   
> diff --git a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> index 84e085ebfbf..6279e24ba79 100644
> --- a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> +++ b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> @@ -61,3 +61,4 @@ test03()
>   
>   // { dg-error "tuple index must be in range" "" { target *-*-* } 0 }
>   // { dg-prune-output "no type named 'type' in .*_Nth_type" }
> +// { dg-prune-output "'__builtin_type_pack_element' index is out of range" }


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2022-07-07 19:28 ` Jason Merrill
@ 2022-07-07 20:46   ` Jonathan Wakely
  0 siblings, 0 replies; 14+ messages in thread
From: Jonathan Wakely @ 2022-07-07 20:46 UTC (permalink / raw)
  To: Jason Merrill; +Cc: libstdc++, gcc Patches, Marek Polacek

On Thu, 7 Jul 2022 at 20:29, Jason Merrill <jason@redhat.com> wrote:
>
> On 7/7/22 13:14, Jonathan Wakely wrote:
> > This adds a new built-in to replace the recursive class template
> > instantiations done by traits such as std::tuple_element and
> > std::variant_alternative. The purpose is to select the Nth type from a
> > list of types, e.g. __builtin_type_pack_element(1, char, int, float) is
> > int.
> >
> > For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> > the compilation time is reduced by more than 90% and the memory  used by
> > the compiler is reduced by 97%. In realistic examples the gains will be
> > much smaller, but still relevant.
> >
> > Clang has a similar built-in, __type_pack_element<N, T...>, but that's a
> > "magic template" built-in using <> syntax, which GCC doesn't support. So
> > this provides an equivalent feature, but as a built-in function using
> > parens instead of <>. I don't really like the name "type pack element"
> > (it gives you an element from a pack of types) but the semi-consistency
> > with Clang seems like a reasonable argument in favour of keeping the
> > name. I'd be open to alternative names though, e.g. __builtin_nth_type
> > or __builtin_type_at_index.
> >
> >
> > The patch has some problems though ...
> >
> > FIXME 1: Marek pointed out that this this ICEs:
> > template<class... T> using type = __builtin_type_pack_element(sizeof(T), T...);
> > type<int, char> c;
> >
> > The sizeof(T) expression is invalid, because T is an unexpanded pack,
> > but it's not rejected and instead crashes:
> >
> > ice.C: In substitution of 'template<class ... T> using type = __builtin_type_pack_element (sizeof (T), T ...) [with T = {int, char}]':
> > ice.C:2:15:   required from here
> > ice.C:1:63: internal compiler error: in dependent_type_p, at cp/pt.cc:27490
> >      1 | template<class... T> using type = __builtin_type_pack_element(sizeof(T), T...);
> >        |                                                               ^~~~~~~~~
> > 0xe13eea dependent_type_p(tree_node*)
> >          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:27490
> > 0xeb1286 cxx_sizeof_or_alignof_type(unsigned int, tree_node*, tree_code, bool, bool)
> >          /home/jwakely/src/gcc/gcc/gcc/cp/typeck.cc:1912
> > 0xdf4fcc tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
> >          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:20582
> > 0xdd9121 tsubst_tree_list(tree_node*, tree_node*, int, tree_node*)
> >          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15587
> > 0xddb583 tsubst(tree_node*, tree_node*, int, tree_node*)
> >          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:16056
> > 0xddcc9d tsubst(tree_node*, tree_node*, int, tree_node*)
> >          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:16436
> > 0xdd6d45 tsubst_decl
> >          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15038
> > 0xdd952a tsubst(tree_node*, tree_node*, int, tree_node*)
> >          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15668
> > 0xdfb9a1 instantiate_template(tree_node*, tree_node*, int)
> >          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:21811
> > 0xdfc1b6 instantiate_alias_template
> >          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:21896
> > 0xdd9796 tsubst(tree_node*, tree_node*, int, tree_node*)
> >          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15696
> > 0xdbaba5 lookup_template_class(tree_node*, tree_node*, tree_node*, tree_node*, int, int)
> >          /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:10131
> > 0xe4bac0 finish_template_type(tree_node*, tree_node*, int)
> >          /home/jwakely/src/gcc/gcc/gcc/cp/semantics.cc:3727
> > 0xd334c8 cp_parser_template_id
> >          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:18458
> > 0xd429b0 cp_parser_class_name
> >          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:25923
> > 0xd1ade9 cp_parser_qualifying_entity
> >          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:7193
> > 0xd1a2c8 cp_parser_nested_name_specifier_opt
> >          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:6875
> > 0xd4eefd cp_parser_template_introduction
> >          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:31668
> > 0xd4f416 cp_parser_template_declaration_after_export
> >          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:31840
> > 0xd2d60e cp_parser_declaration
> >          /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:15083
> >
> >
> > FIXME 2: I want to mangle __builtin_type_pack_element(N, T...) the same as
> > typename std::_Nth_type<N, T...>::type but I don't know how. Instead of
> > trying to fake the mangled string, it's probably better to build a decl
> > for that nested type, right? Any suggestions where to find something
> > similar I can learn from?
>
> The tricky thing is dealing with mangling compression, where we use a
> substitution instead of repeating a type; that's definitely easier if we
> actually have the type.

Yeah, that's what I discovered when trying to fudge it as
"19__builtin_type_pack_elementE" etc.

> So you'd probably want to have a declaration of std::_Nth_type to work
> with, and lookup_template_class to get the type of that specialization.
>   And then if it's complete, look up ...::type; if not, we could
> probably stuff a ...::type in its TYPE_FIELDS that would get clobbered
> if we actually instantiated the type...
>
> > The reason to mangle it that way is that it preserves the same symbol
> > names as the library produced in GCC 12, and that it will still produce
> > with non-GCC compilers (see the definitions of std::_Nth_type in the
> > library parts of the patch). If we don't do that then either we need to
> > ensure it never appears in a mangled name, or define some other
> > GCC-specific mangling for this built-in (e.g. we could just mangle it as
> > though it's a function, "19__builtin_type_pack_elementELm1EJDpT_E" or
> > something like that!). If we ensure it doesn't appear in mangled names
> > that means we still need to instantiate the _Nth_type class template,
> > rather than defining the alias template _Nth_type_t to use the built-in
> > directly.  That loses a little of the compilation performance gain that
> > comes from defining the built-in in the first place (although avoid the
> > recusrion is the biggest gain, which we'd still get).
>
> ...but this seems a lot simpler.

Yeah, it works fine, and if the built-in ever accidentally creeps into
a mangled name we get a sorry, not an incorrect mangling. OK, I'll
keep it simple.

I'll try Marek's fix for the ICE and will not worry about mangling for
now. We can revisit later if we decide to care more about it.


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2022-07-07 17:14 [PATCH] c++: Define built-in for std::tuple_element [PR100157] Jonathan Wakely
  2022-07-07 17:25 ` Marek Polacek
  2022-07-07 19:28 ` Jason Merrill
@ 2022-10-05 13:43 ` Patrick Palka
  2023-01-09 15:53   ` Patrick Palka
  2 siblings, 1 reply; 14+ messages in thread
From: Patrick Palka @ 2022-10-05 13:43 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: libstdc++, gcc-patches, Marek Polacek

On Thu, 7 Jul 2022, Jonathan Wakely via Gcc-patches wrote:

> This adds a new built-in to replace the recursive class template
> instantiations done by traits such as std::tuple_element and
> std::variant_alternative. The purpose is to select the Nth type from a
> list of types, e.g. __builtin_type_pack_element(1, char, int, float) is
> int.
> 
> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> the compilation time is reduced by more than 90% and the memory  used by
> the compiler is reduced by 97%. In realistic examples the gains will be
> much smaller, but still relevant.
> 
> Clang has a similar built-in, __type_pack_element<N, T...>, but that's a
> "magic template" built-in using <> syntax, which GCC doesn't support. So
> this provides an equivalent feature, but as a built-in function using
> parens instead of <>. I don't really like the name "type pack element"
> (it gives you an element from a pack of types) but the semi-consistency
> with Clang seems like a reasonable argument in favour of keeping the
> name. I'd be open to alternative names though, e.g. __builtin_nth_type
> or __builtin_type_at_index.

Rather than giving the trait a different name from __type_pack_element,
I wonder if we could just special case cp_parser_trait to expect <>
instead of parens for this trait?

Btw the frontend recently got a generic TRAIT_TYPE tree code, which gets
rid of much of the boilerplate of adding a new type-yielding built-in
trait, see e.g. cp-trait.def.

> 
> 
> The patch has some problems though ...
> 
> FIXME 1: Marek pointed out that this this ICEs:
> template<class... T> using type = __builtin_type_pack_element(sizeof(T), T...);
> type<int, char> c;
> 
> The sizeof(T) expression is invalid, because T is an unexpanded pack,
> but it's not rejected and instead crashes:
> 
> ice.C: In substitution of 'template<class ... T> using type = __builtin_type_pack_element (sizeof (T), T ...) [with T = {int, char}]':
> ice.C:2:15:   required from here
> ice.C:1:63: internal compiler error: in dependent_type_p, at cp/pt.cc:27490
>     1 | template<class... T> using type = __builtin_type_pack_element(sizeof(T), T...);
>       |                                                               ^~~~~~~~~
> 0xe13eea dependent_type_p(tree_node*)
>         /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:27490
> 0xeb1286 cxx_sizeof_or_alignof_type(unsigned int, tree_node*, tree_code, bool, bool)
>         /home/jwakely/src/gcc/gcc/gcc/cp/typeck.cc:1912
> 0xdf4fcc tsubst_copy_and_build(tree_node*, tree_node*, int, tree_node*, bool, bool)
>         /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:20582
> 0xdd9121 tsubst_tree_list(tree_node*, tree_node*, int, tree_node*)
>         /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15587
> 0xddb583 tsubst(tree_node*, tree_node*, int, tree_node*)
>         /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:16056
> 0xddcc9d tsubst(tree_node*, tree_node*, int, tree_node*)
>         /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:16436
> 0xdd6d45 tsubst_decl
>         /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15038
> 0xdd952a tsubst(tree_node*, tree_node*, int, tree_node*)
>         /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15668
> 0xdfb9a1 instantiate_template(tree_node*, tree_node*, int)
>         /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:21811
> 0xdfc1b6 instantiate_alias_template
>         /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:21896
> 0xdd9796 tsubst(tree_node*, tree_node*, int, tree_node*)
>         /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:15696
> 0xdbaba5 lookup_template_class(tree_node*, tree_node*, tree_node*, tree_node*, int, int)
>         /home/jwakely/src/gcc/gcc/gcc/cp/pt.cc:10131
> 0xe4bac0 finish_template_type(tree_node*, tree_node*, int)
>         /home/jwakely/src/gcc/gcc/gcc/cp/semantics.cc:3727
> 0xd334c8 cp_parser_template_id
>         /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:18458
> 0xd429b0 cp_parser_class_name
>         /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:25923
> 0xd1ade9 cp_parser_qualifying_entity
>         /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:7193
> 0xd1a2c8 cp_parser_nested_name_specifier_opt
>         /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:6875
> 0xd4eefd cp_parser_template_introduction
>         /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:31668
> 0xd4f416 cp_parser_template_declaration_after_export
>         /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:31840
> 0xd2d60e cp_parser_declaration
>         /home/jwakely/src/gcc/gcc/gcc/cp/parser.cc:15083
> 
> 
> FIXME 2: I want to mangle __builtin_type_pack_element(N, T...) the same as
> typename std::_Nth_type<N, T...>::type but I don't know how. Instead of
> trying to fake the mangled string, it's probably better to build a decl
> for that nested type, right? Any suggestions where to find something
> similar I can learn from?
> 
> The reason to mangle it that way is that it preserves the same symbol
> names as the library produced in GCC 12, and that it will still produce
> with non-GCC compilers (see the definitions of std::_Nth_type in the
> library parts of the patch). If we don't do that then either we need to
> ensure it never appears in a mangled name, or define some other
> GCC-specific mangling for this built-in (e.g. we could just mangle it as
> though it's a function, "19__builtin_type_pack_elementELm1EJDpT_E" or
> something like that!). If we ensure it doesn't appear in mangled names
> that means we still need to instantiate the _Nth_type class template,
> rather than defining the alias template _Nth_type_t to use the built-in
> directly.  That loses a little of the compilation performance gain that
> comes from defining the built-in in the first place (although avoid the
> recusrion is the biggest gain, which we'd still get).
> 
> 
> 
> FIXME 3:This change regresses diagnostics from std::tuple_element
> because as well as the existing:
> 
> tuple:1357: error: static assertion failed: tuple index must be in range
> 
> we now also get:
> 
> bits/utility.h:231: error: '__builtin_type_pack_element' index is out of range
> 
> which is not very user-friendly.
> 
> Maybe that should not mention the built-in by name, and just say
> something generic like "out of range index into type pack". Or maybe
> diagnose it as std::_Nth_type_t, like I plan to mangle it. That would be
> a bit confusing for anybody using the built-in directly, but I'm sure
> they can live with it - they're already using a non-portable intrinsic.
> 
> That still means two errors where we used to only print one though.
> Maybe the library needs to try harder to not let invalid indices reach
> the built-in. Currently we have:
> 
>   template<size_t __i, typename... _Types>
>     struct tuple_element<__i, tuple<_Types...>>
>     {
>       static_assert(__i < sizeof...(_Types), "tuple index must be in range");
> 
>       using type = _Nth_type_t<__i, _Types...>;
>     };
> 
> And _Nth_type_t is an alias for a type defined using the built-in.
> 
> Maybe I need to add another level of indirection (losing some of the
> compile-time improvements that the built-in is supposed to provide) or
> use concepts to constrain _Nth_type_t (which doesn't help pre-C++20).
> 
> This seems like another case where the compiler should just stop when a
> static assert fails. The whole point of that assertion is that what
> follows doesn't make sense unless it passes.
> 
> 
> Suggestions for how to fix these issues are welcome, I think I've gone
> as far as I can for now.
> 
> 
> -- >8 --
> 
> Add __builtin_type_pack_element so that std::tuple_element and
> std::variant_alternative can be implemented efficiently, without
> recursive class template instantiations.
> 
> The name is intended to be similar to Clang's __type_pack_element<>
> built-in which has the same behaviour, but uses template syntax instead
> of function-call syntax.
> 
> The libstdc++ headers can be updated to use this new built-in, or
> Clang's equivalent. Until the FIXME in mangle.cc:write_type is fixed
> (i.e. mangling for the built-in is added) we can't defined the
> _Nth_type_t alias template to use the built-in directly, because
> function templates that use the alias would need to mangle the built-in.
> I suggest mangling it as typename _Nth_type<N, T...>::type which will be
> backwards compatible with symbol names from GCC 12 and Clang.
> 
> 	PR c++/100157
> 
> gcc/c-family/ChangeLog:
> 
> 	* c-common.cc (c_common_reswords): Add
> 	__builtin_type_pack_element.
> 	* c-common.h (enum rid): Add RID_TYPE_PACK_ELEMENT.
> 
> gcc/cp/ChangeLog:
> 
> 	* constraint.cc (diagnose_trait_expr): Add
> 	CPTK_TYPE_PACK_ELEMENT to switch.
> 	* cp-objcp-common.cc (names_builtin_p): Add
> 	RID_TYPE_PACK_ELEMENT to switch.
> 	(cp_common_init_ts): Mark TYPE_PACK_ELEMENT as not having a
> 	common member.
> 	* cp-tree.def (TYPE_PACK_ELEMENT): Define tree code.
> 	* cp-tree.h (enum cp_trait_kind): Add CPTK_TYPE_PACK_ELEMENT.
> 	(TYPE_PACK_ELEMENT_ARGS): Define.
> 	(finish_type_pack_element): Declare.
> 	* error.cc (dump_type): Add TYPE_PACK_ELEMENT to switch.
> 	(dump_type_prefix): Likewise.
> 	(dump_type_suffix): Likewise.
> 	* mangle.cc (write_type): Likewise.
> 	* parser.cc (cp_keyword_starts_decl_specifier_p): Add
> 	RID_TYPE_PACK_ELEMENT to switch.
> 	(cp_parser_trait_expr): Likewise. Parse its arguments and call
> 	finish_type_pack_element.
> 	(cp_parser_simple_type_specifier): Add RID_TYPE_PACK_ELEMENT to
> 	switch.
> 	* pt.cc (for_each_template_parm_r): Add TYPE_PACK_ELEMENT to
> 	switch.
> 	(tsubst): Likewise.
> 	(unify): Likewise.
> 	(dependent_type_p_r): A TYPE_PACK_ELEMENT is dependent.
> 	* semantics.cc (finish_type_pack_element): New function.
> 
> gcc/ChangeLog:
> 
> 	* doc/extend.texi (Type Traits): Document new built-in.
> 
> libstdc++-v3/ChangeLog:
> 
> 	* include/bits/utility.h (_Nth_type_t): New alias template using
> 	built-ins when available.
> 	* include/std/tuple: Use _Nth_type_t instead of _Nth_type.
> 	* include/std/variant: Likewise.
> 	* testsuite/20_util/tuple/element_access/get_neg.cc: Prune
> 	additional errors from the new built-in.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/ext/type_pack_element1.C: New test.
> 	* g++.dg/ext/type_pack_element2.C: New test.
> ---
>  gcc/c-family/c-common.cc                      |  1 +
>  gcc/c-family/c-common.h                       |  1 +
>  gcc/cp/constraint.cc                          |  1 +
>  gcc/cp/cp-objcp-common.cc                     |  2 +
>  gcc/cp/cp-tree.def                            |  4 ++
>  gcc/cp/cp-tree.h                              |  6 +++
>  gcc/cp/error.cc                               | 16 +++++++
>  gcc/cp/mangle.cc                              |  5 +++
>  gcc/cp/parser.cc                              | 39 ++++++++++++++--
>  gcc/cp/pt.cc                                  | 18 ++++++--
>  gcc/cp/semantics.cc                           | 44 +++++++++++++++++++
>  gcc/doc/extend.texi                           |  6 +++
>  gcc/testsuite/g++.dg/ext/type_pack_element1.C | 26 +++++++++++
>  gcc/testsuite/g++.dg/ext/type_pack_element2.C | 31 +++++++++++++
>  libstdc++-v3/include/bits/utility.h           | 26 +++++++++++
>  libstdc++-v3/include/std/tuple                |  2 +-
>  libstdc++-v3/include/std/variant              | 24 +++++-----
>  .../20_util/tuple/element_access/get_neg.cc   |  1 +
>  18 files changed, 232 insertions(+), 21 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element1.C
>  create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element2.C
> 
> diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> index 1b8e73f7bc5..655b571a4ee 100644
> --- a/gcc/c-family/c-common.cc
> +++ b/gcc/c-family/c-common.cc
> @@ -389,6 +389,7 @@ const struct c_common_resword c_common_reswords[] =
>    { "__builtin_shufflevector", RID_BUILTIN_SHUFFLEVECTOR, 0 },
>    { "__builtin_tgmath", RID_BUILTIN_TGMATH, D_CONLY },
>    { "__builtin_offsetof", RID_OFFSETOF, 0 },
> +  { "__builtin_type_pack_element", RID_TYPE_PACK_ELEMENT, D_CXXONLY },
>    { "__builtin_types_compatible_p", RID_TYPES_COMPATIBLE_P, D_CONLY },
>    { "__builtin_va_arg",	RID_VA_ARG,	0 },
>    { "__complex",	RID_COMPLEX,	0 },
> diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> index c0900848965..e9d0864f2ba 100644
> --- a/gcc/c-family/c-common.h
> +++ b/gcc/c-family/c-common.h
> @@ -184,6 +184,7 @@ enum rid
>    RID_IS_UNION,                RID_UNDERLYING_TYPE,
>    RID_IS_ASSIGNABLE,           RID_IS_CONSTRUCTIBLE,
>    RID_IS_NOTHROW_ASSIGNABLE,   RID_IS_NOTHROW_CONSTRUCTIBLE,
> +  RID_TYPE_PACK_ELEMENT,
>  
>    /* C++11 */
>    RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, RID_STATIC_ASSERT,
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 591155cee22..f7bd189ae47 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -3690,6 +3690,7 @@ diagnose_trait_expr (tree expr, tree args)
>      case CPTK_BASES:
>      case CPTK_DIRECT_BASES:
>      case CPTK_UNDERLYING_TYPE:
> +    case CPTK_TYPE_PACK_ELEMENT:
>        /* We shouldn't see these non-expression traits.  */
>        gcc_unreachable ();
>      /* We deliberately omit the default case so that when adding a new
> diff --git a/gcc/cp/cp-objcp-common.cc b/gcc/cp/cp-objcp-common.cc
> index 0b70d5567e4..bac6b5a6214 100644
> --- a/gcc/cp/cp-objcp-common.cc
> +++ b/gcc/cp/cp-objcp-common.cc
> @@ -461,6 +461,7 @@ names_builtin_p (const char *name)
>      case RID_IS_ASSIGNABLE:
>      case RID_IS_CONSTRUCTIBLE:
>      case RID_UNDERLYING_TYPE:
> +    case RID_TYPE_PACK_ELEMENT:
>        return true;
>      default:
>        break;
> @@ -517,6 +518,7 @@ cp_common_init_ts (void)
>    MARK_TS_TYPE_NON_COMMON (TEMPLATE_TEMPLATE_PARM);
>    MARK_TS_TYPE_NON_COMMON (TEMPLATE_TYPE_PARM);
>    MARK_TS_TYPE_NON_COMMON (TYPE_PACK_EXPANSION);
> +  MARK_TS_TYPE_NON_COMMON (TYPE_PACK_ELEMENT);
>  
>    /* Statements.  */
>    MARK_TS_EXP (CLEANUP_STMT);
> diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def
> index f9cbd339f19..6ea197f16af 100644
> --- a/gcc/cp/cp-tree.def
> +++ b/gcc/cp/cp-tree.def
> @@ -470,6 +470,10 @@ DEFTREECODE (DECLTYPE_TYPE, "decltype_type", tcc_type, 0)
>     UNDERLYING_TYPE_TYPE is the type in question.  */
>  DEFTREECODE (UNDERLYING_TYPE, "underlying_type", tcc_type, 0)
>  
> +/* A type designated by `__builtin_type_pack_element (n, type)'.
> +   TYPE_PACK_ELEMENT_ARGS contains the arguments.  */
> +DEFTREECODE (TYPE_PACK_ELEMENT, "builtin_type_pack_element", tcc_type, 0)
> +
>  /* A type designated by one of the bases type traits.
>     BASES_TYPE is the type in question.  */
>  DEFTREECODE (BASES, "bases", tcc_type, 0)
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 2fde4f83b41..2430b0b94c1 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -1393,6 +1393,7 @@ enum cp_trait_kind
>    CPTK_IS_TRIVIALLY_CONSTRUCTIBLE,
>    CPTK_IS_TRIVIALLY_COPYABLE,
>    CPTK_IS_UNION,
> +  CPTK_TYPE_PACK_ELEMENT,
>    CPTK_UNDERLYING_TYPE,
>    CPTK_IS_ASSIGNABLE,
>    CPTK_IS_CONSTRUCTIBLE,
> @@ -4789,6 +4790,10 @@ get_vec_init_expr (tree t)
>  #define UNDERLYING_TYPE_TYPE(NODE) \
>    (TYPE_VALUES_RAW (UNDERLYING_TYPE_CHECK (NODE)))
>  
> +/* The arguments for a TYPE_PACK_ELEMENT.  */
> +#define TYPE_PACK_ELEMENT_ARGS(NODE) \
> +  (TYPE_VALUES_RAW (TYPE_PACK_ELEMENT_CHECK (NODE)))
> +
>  /* The type in question for BASES.  */
>  #define BASES_TYPE(NODE) \
>    (TYPE_VALUES_RAW (BASES_CHECK (NODE)))
> @@ -7640,6 +7645,7 @@ extern cp_expr finish_id_expression		(tree, tree, tree,
>                                                   location_t);
>  extern tree finish_typeof			(tree);
>  extern tree finish_underlying_type	        (tree);
> +extern tree finish_type_pack_element	        (tree, tree);
>  extern tree calculate_bases                     (tree, tsubst_flags_t);
>  extern tree finish_bases                        (tree, bool);
>  extern tree calculate_direct_bases              (tree, tsubst_flags_t);
> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> index 94181e76d0e..1b6f4df51b9 100644
> --- a/gcc/cp/error.cc
> +++ b/gcc/cp/error.cc
> @@ -706,6 +706,20 @@ dump_type (cxx_pretty_printer *pp, tree t, int flags)
>        pp_cxx_right_paren (pp);
>        break;
>  
> +    case TYPE_PACK_ELEMENT:
> +      t = TYPE_PACK_ELEMENT_ARGS (t);
> +      pp_cxx_ws_string (pp, "__builtin_type_pack_element");
> +      pp_cxx_whitespace (pp);
> +      pp_cxx_left_paren (pp);
> +      dump_expr (pp, TREE_VALUE (t), flags & ~TFF_EXPR_IN_PARENS);
> +      for (tree arg = TREE_CHAIN (t); arg; arg = TREE_CHAIN (arg))
> +	{
> +	  pp_separate_with_comma (pp);
> +	  dump_type (pp, TREE_VALUE (arg), flags);
> +	}
> +      pp_cxx_right_paren (pp);
> +      break;
> +
>      case TYPE_PACK_EXPANSION:
>        dump_type (pp, PACK_EXPANSION_PATTERN (t), flags);
>        pp_cxx_ws_string (pp, "...");
> @@ -974,6 +988,7 @@ dump_type_prefix (cxx_pretty_printer *pp, tree t, int flags)
>      case UNDERLYING_TYPE:
>      case DECLTYPE_TYPE:
>      case TYPE_PACK_EXPANSION:
> +    case TYPE_PACK_ELEMENT:
>      case FIXED_POINT_TYPE:
>      case NULLPTR_TYPE:
>        dump_type (pp, t, flags);
> @@ -1098,6 +1113,7 @@ dump_type_suffix (cxx_pretty_printer *pp, tree t, int flags)
>      case UNDERLYING_TYPE:
>      case DECLTYPE_TYPE:
>      case TYPE_PACK_EXPANSION:
> +    case TYPE_PACK_ELEMENT:
>      case FIXED_POINT_TYPE:
>      case NULLPTR_TYPE:
>        break;
> diff --git a/gcc/cp/mangle.cc b/gcc/cp/mangle.cc
> index 75388e99bfd..fbaed426940 100644
> --- a/gcc/cp/mangle.cc
> +++ b/gcc/cp/mangle.cc
> @@ -2393,6 +2393,11 @@ write_type (tree type)
>  	      sorry ("mangling %<__underlying_type%>");
>  	      break;
>  
> +	    case TYPE_PACK_ELEMENT:
> +	      /* FIXME: Mangle as std::_Nth_type<N, T...>::type.  */
> +	      sorry ("mangling %<__builtin_type_pack_element%>");
> +	      break;
> +
>  	    case LANG_TYPE:
>  	      /* fall through.  */
>  
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index bf9ea3685f8..8f0f0331d4f 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -1142,6 +1142,7 @@ cp_keyword_starts_decl_specifier_p (enum rid keyword)
>        /* C++11 extensions.  */
>      case RID_DECLTYPE:
>      case RID_UNDERLYING_TYPE:
> +    case RID_TYPE_PACK_ELEMENT:
>      case RID_CONSTEXPR:
>        /* C++20 extensions.  */
>      case RID_CONSTINIT:
> @@ -10966,6 +10967,10 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
>      case RID_UNDERLYING_TYPE:
>        kind = CPTK_UNDERLYING_TYPE;
>        break;
> +    case RID_TYPE_PACK_ELEMENT:
> +      kind = CPTK_TYPE_PACK_ELEMENT;
> +      variadic = true;
> +      break;
>      case RID_BASES:
>        kind = CPTK_BASES;
>        break;
> @@ -11001,10 +11006,24 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
>    matching_parens parens;
>    parens.require_open (parser);
>  
> -  {
> -    type_id_in_expr_sentinel s (parser);
> -    type1 = cp_parser_type_id (parser);
> -  }
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    {
> +      cp_expr e = cp_parser_constant_expression (parser, 0, nullptr, true);
> +      if (!cp_lexer_next_token_is (parser->lexer, CPP_COMMA))
> +	{
> +	  location_t err_loc = cp_lexer_peek_token (parser->lexer)->location;
> +	  err_loc = make_location (err_loc, start_loc, err_loc);
> +	  error_at (err_loc, "%<__builtin_type_pack_element%> requires"
> +			      " one or more type arguments");
> +	  return error_mark_node;
> +	}
> +      type1 = e.get_value (); // actually a constant-expression, not a type
> +    }
> +  else
> +    {
> +      type_id_in_expr_sentinel s (parser);
> +      type1 = cp_parser_type_id (parser);
> +    }
>  
>    if (type1 == error_mark_node)
>      return error_mark_node;
> @@ -11054,6 +11073,8 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
>      {
>      case CPTK_UNDERLYING_TYPE:
>        return cp_expr (finish_underlying_type (type1), trait_loc);
> +    case CPTK_TYPE_PACK_ELEMENT:
> +      return cp_expr (finish_type_pack_element (type1, type2), trait_loc);
>      case CPTK_BASES:
>        return cp_expr (finish_bases (type1, false), trait_loc);
>      case CPTK_DIRECT_BASES:
> @@ -19571,6 +19592,7 @@ cp_parser_type_specifier (cp_parser* parser,
>       char16_t
>       char32_t
>       __underlying_type ( type-id )
> +     __builtin_type_pack_element ( constant-expression , type-id , [opt] )
>  
>     C++17 extension:
>  
> @@ -19783,6 +19805,15 @@ cp_parser_simple_type_specifier (cp_parser* parser,
>  
>        return type;
>  
> +    case RID_TYPE_PACK_ELEMENT:
> +      type = cp_parser_trait_expr (parser, RID_TYPE_PACK_ELEMENT);
> +      if (decl_specs)
> +	cp_parser_set_decl_spec_type (decl_specs, type,
> +				      token,
> +				      /*type_definition_p=*/false);
> +
> +      return type;
> +
>      case RID_BASES:
>      case RID_DIRECT_BASES:
>        type = cp_parser_trait_expr (parser, token->keyword);
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 8672da123f4..590b3eccc84 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -10592,6 +10592,7 @@ for_each_template_parm_r (tree *tp, int *walk_subtrees, void *d)
>      case TYPEOF_TYPE:
>      case DECLTYPE_TYPE:
>      case UNDERLYING_TYPE:
> +    case TYPE_PACK_ELEMENT:
>        if (pfd->include_nondeduced_p
>  	  && for_each_template_parm (TYPE_VALUES_RAW (t), fn, data,
>  				     pfd->visited,
> @@ -16430,6 +16431,15 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>  	return finish_underlying_type (type);
>        }
>  
> +    case TYPE_PACK_ELEMENT:
> +      {
> +	tree subst_args = tsubst (TYPE_PACK_ELEMENT_ARGS (t), args,
> +				  complain, in_decl);
> +	tree pack_index = TREE_VALUE (subst_args);
> +	tree types = TREE_CHAIN (subst_args);
> +	return finish_type_pack_element (pack_index, types);
> +      }
> +
>      case TYPE_ARGUMENT_PACK:
>      case NONTYPE_ARGUMENT_PACK:
>        return tsubst_argument_pack (t, args, complain, in_decl);
> @@ -24829,8 +24839,9 @@ unify (tree tparms, tree targs, tree parm, tree arg, int strict,
>      case TYPEOF_TYPE:
>      case DECLTYPE_TYPE:
>      case UNDERLYING_TYPE:
> +    case TYPE_PACK_ELEMENT:
>        /* Cannot deduce anything from TYPEOF_TYPE, DECLTYPE_TYPE,
> -	 or UNDERLYING_TYPE nodes.  */
> +	 UNDERLYING_TYPE, or TYPE_PACK_ELEMENT nodes.  */
>        return unify_success (explain_p);
>  
>      case ERROR_MARK:
> @@ -27405,12 +27416,13 @@ dependent_type_p_r (tree type)
>  	       (INNERMOST_TEMPLATE_ARGS (CLASSTYPE_TI_ARGS (type)))))
>      return true;
>  
> -  /* All TYPEOF_TYPEs, DECLTYPE_TYPEs, and UNDERLYING_TYPEs are
> +  /* All these are
>       dependent; if the argument of the `typeof' expression is not
>       type-dependent, then it should already been have resolved.  */
>    if (TREE_CODE (type) == TYPEOF_TYPE
>        || TREE_CODE (type) == DECLTYPE_TYPE
> -      || TREE_CODE (type) == UNDERLYING_TYPE)
> +      || TREE_CODE (type) == UNDERLYING_TYPE
> +      || TREE_CODE (type) == TYPE_PACK_ELEMENT)
>      return true;
>  
>    /* A template argument pack is dependent if any of its packed
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index 2344b5eea00..36eff75ed45 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -4418,6 +4418,50 @@ finish_underlying_type (tree type)
>    return underlying_type;
>  }
>  
> +/* Implement the __builtin_type_pack_element keyword: Return the type
> +   at index N in TYPES..., suitable for use as a type-specifier.  */
> +
> +tree
> +finish_type_pack_element (tree n, tree types)
> +{
> +  if (n == error_mark_node
> +      || types == error_mark_node)
> +    return error_mark_node;
> +
> +  if (processing_template_decl)
> +    {
> +      if (value_dependent_expression_p (n) || uses_template_parms (types))
> +	{
> +	  tree t = cxx_make_type (TYPE_PACK_ELEMENT);
> +	  TYPE_PACK_ELEMENT_ARGS (t) = tree_cons (NULL_TREE, n, types);
> +	  return t;
> +	}
> +    }
> +
> +  n = fold_non_dependent_expr (n);
> +
> +  if (!INTEGRAL_TYPE_P (TREE_TYPE (n)) || TREE_CODE (n) != INTEGER_CST)
> +    {
> +      error ("%<__builtin_type_pack_element%> index is not an integral"
> +	     " constant");
> +      return error_mark_node;
> +    }
> +
> +  HOST_WIDE_INT lunroll = tree_to_shwi (n);
> +  if (lunroll < 0 || lunroll >= list_length (types))
> +    {
> +      error ("%<__builtin_type_pack_element%> index is out of range");
> +      return error_mark_node;
> +    }
> +
> +  unsigned index = (unsigned)lunroll;
> +  while (index-- > 0 && types != NULL_TREE)
> +    types = TREE_CHAIN (types);
> +  if (types == NULL_TREE)
> +    return error_mark_node;
> +  return TREE_VALUE (types);
> +}
> +
>  /* Implement the __direct_bases keyword: Return the direct base classes
>     of type.  */
>  
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index dfbe33ac652..5f0f39fe72e 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -25201,6 +25201,12 @@ definition, expands to a template argument pack containing integers
>  from @code{0} to @code{length-1}.  This is provided for efficient
>  implementation of @code{std::make_integer_sequence}.
>  
> +@item __builtin_type_pack_element (n, types...)
> +The Nth type in the list of types.
> +This is provided for efficient
> +implementation of @code{std::tuple_element} and similar.
> +Requires: 0 @leq{} @code{n} < number of type arguments.
> +
>  @end table
>  
>  
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element1.C b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> new file mode 100644
> index 00000000000..cc4b6b4b67f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> @@ -0,0 +1,26 @@
> +// { dg-do compile { target c++11 } }
> +
> +template<class, class> struct is_same { static constexpr bool value = false; };
> +template<class T> struct is_same<T,T> { static constexpr bool value = true; };
> +
> +static_assert( is_same<__builtin_type_pack_element(0, int), int>::value, "" );
> +static_assert( is_same<__builtin_type_pack_element(0, long), long>::value, "" );
> +static_assert( is_same<__builtin_type_pack_element(1, float, char, int), char>::value, "" );
> +
> +using T = __builtin_type_pack_element(sizeof('0'), int, int);
> +
> +template<int N, class... T>
> +using Nth_type = __builtin_type_pack_element(N, T...);
> +
> +static_assert( is_same<Nth_type<0, int>, int>::value, "" );
> +static_assert( is_same<Nth_type<0, long>, long>::value, "" );
> +static_assert( is_same<Nth_type<1, float, char, int>, char>::value, "" );
> +
> +template<int N>
> +struct Nth_type_class_template
> +{
> +  using type = __builtin_type_pack_element(N, int, void, char, float, long);
> +};
> +
> +static_assert( is_same<typename Nth_type_class_template<0>::type, int>::value, "" );
> +static_assert( is_same<typename Nth_type_class_template<1>::type, void>::value, "" );
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element2.C b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> new file mode 100644
> index 00000000000..04040e0e84f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> @@ -0,0 +1,31 @@
> +// { dg-do compile { target c++11 } }
> +// { dg-options -w }
> +
> +using X1 = __builtin_type_pack_element(1);  // { dg-error "one or more type" }
> +using X2 = __builtin_type_pack_element(1, 1); // { dg-error "expected type" }
> +int i;
> +using X3 = __builtin_type_pack_element(1, i); // { dg-error "does not name a type" }
> +using X4 = __builtin_type_pack_element(1, int); // { dg-error "out of range" }
> +
> +using X5 = __builtin_type_pack_element(-1, int); // { dg-error "out of range" }
> +using X6 = __builtin_type_pack_element(nullptr, int); // { dg-error "integral" }
> +
> +template<int N, class T>
> +struct uninstantiated_template
> +{
> +  using X = __builtin_type_pack_element(2, int); // { dg-error "out of range" }
> +  using Y = __builtin_type_pack_element(2, T);   // { dg-bogus "out of range" }
> +  using Z = __builtin_type_pack_element(N, int); // { dg-bogus "." }
> +};
> +
> +
> +template<int N, class T>
> +struct instantiated_template
> +{
> +  using Y = __builtin_type_pack_element(2, T); // { dg-error "out of range" }
> +  using Z = __builtin_type_pack_element(N, T); // { dg-error "out of range" }
> +};
> +
> +using Y = typename instantiated_template<0, int>::Y;
> +using Z = typename instantiated_template<1, int>::Z;
> +// { dg-prune-output "invalid combination of multiple type-specifiers" }
> diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
> index e0e40309a6d..0b105e0df1b 100644
> --- a/libstdc++-v3/include/bits/utility.h
> +++ b/libstdc++-v3/include/bits/utility.h
> @@ -224,6 +224,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  #endif // C++17
>  #endif // C++14
>  
> +#if __has_builtin(__builtin_type_pack_element) // GCC
> +
> +  template<size_t _Np, typename... _Types>
> +    struct _Nth_type
> +    { using type = __builtin_type_pack_element(_Np, _Types...); };
> +
> +  // FIXME: use built-in directly, but requires mangling for the built-in.
> +  template<size_t _Np, typename... _Types>
> +    using _Nth_type_t = typename _Nth_type<_Np, _Types...>::type;
> +
> +#elif __has_builtin(__type_pack_element) // Clang
> +
> +  template<size_t _Np, typename... _Types>
> +    struct _Nth_type
> +    { using type = __type_pack_element<_Np, _Types...>; };
> +
> +  // Defined this way to keep the mangling compatible.
> +  template<size_t _Np, typename... _Types>
> +    using _Nth_type_t = typename _Nth_type<_Np, _Types...>::type;
> +
> +#else // Pure C++ fallback
> +
>    template<size_t _Np, typename... _Types>
>      struct _Nth_type
>      { };
> @@ -263,6 +285,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      { using type = _Tp1; };
>  #endif
>  
> +  template<size_t _Np, typename... _Types>
> +    using _Nth_type_t = typename _Nth_type<_Np, _Types...>::type;
> +#endif // __has_builtin(__builtin_type_pack_element)
> +
>  _GLIBCXX_END_NAMESPACE_VERSION
>  } // namespace
>  
> diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> index 6d0060a191c..8ff1ab42d20 100644
> --- a/libstdc++-v3/include/std/tuple
> +++ b/libstdc++-v3/include/std/tuple
> @@ -1356,7 +1356,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      {
>        static_assert(__i < sizeof...(_Types), "tuple index must be in range");
>  
> -      using type = typename _Nth_type<__i, _Types...>::type;
> +      using type = _Nth_type_t<__i, _Types...>;
>      };
>  
>    template<size_t __i, typename _Head, typename... _Tail>
> diff --git a/libstdc++-v3/include/std/variant b/libstdc++-v3/include/std/variant
> index 5ff1e3edcdf..5cfdaf31e24 100644
> --- a/libstdc++-v3/include/std/variant
> +++ b/libstdc++-v3/include/std/variant
> @@ -98,7 +98,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      {
>        static_assert(_Np < sizeof...(_Types));
>  
> -      using type = typename _Nth_type<_Np, _Types...>::type;
> +      using type = _Nth_type_t<_Np, _Types...>;
>      };
>  
>    template<size_t _Np, typename _Variant>
> @@ -324,7 +324,7 @@ namespace __variant
>      struct _Traits
>      {
>        static constexpr bool _S_default_ctor =
> -	  is_default_constructible_v<typename _Nth_type<0, _Types...>::type>;
> +	  is_default_constructible_v<_Nth_type_t<0, _Types...>>;
>        static constexpr bool _S_copy_ctor =
>  	  (is_copy_constructible_v<_Types> && ...);
>        static constexpr bool _S_move_ctor =
> @@ -352,8 +352,7 @@ namespace __variant
>        // The following nothrow traits are for non-trivial SMFs. Trivial SMFs
>        // are always nothrow.
>        static constexpr bool _S_nothrow_default_ctor =
> -	  is_nothrow_default_constructible_v<
> -	      typename _Nth_type<0, _Types...>::type>;
> +	  is_nothrow_default_constructible_v<_Nth_type_t<0, _Types...>>;
>        static constexpr bool _S_nothrow_copy_ctor = false;
>        static constexpr bool _S_nothrow_move_ctor =
>  	  (is_nothrow_move_constructible_v<_Types> && ...);
> @@ -645,7 +644,7 @@ namespace __variant
>  	      __variant::__get<__j>(*this) = __rhs_mem;
>  	    else
>  	      {
> -		using _Tj = typename _Nth_type<__j, _Types...>::type;
> +		using _Tj = _Nth_type_t<__j, _Types...>;
>  		if constexpr (is_nothrow_copy_constructible_v<_Tj>
>  			      || !is_nothrow_move_constructible_v<_Tj>)
>  		  __variant::__emplace<__j>(*this, __rhs_mem);
> @@ -697,7 +696,7 @@ namespace __variant
>  		  __variant::__get<__j>(*this) = std::move(__rhs_mem);
>  		else
>  		  {
> -		    using _Tj = typename _Nth_type<__j, _Types...>::type;
> +		    using _Tj = _Nth_type_t<__j, _Types...>;
>  		    if constexpr (is_nothrow_move_constructible_v<_Tj>)
>  		      __variant::__emplace<__j>(*this, std::move(__rhs_mem));
>  		    else
> @@ -870,7 +869,7 @@ namespace __variant
>        static constexpr size_t __index =
>  	sizeof...(_Variants) - sizeof...(__rest) - 1;
>  
> -      using _Variant = typename _Nth_type<__index, _Variants...>::type;
> +      using _Variant = _Nth_type_t<__index, _Variants...>;
>  
>        static constexpr int __do_cookie =
>  	__extra_visit_slot_needed<_Ret, _Variant> ? 1 : 0;
> @@ -932,8 +931,7 @@ namespace __variant
>  	std::index_sequence<__indices...>>
>      {
>        using _Next =
> -	  remove_reference_t<typename _Nth_type<sizeof...(__indices),
> -			     _Variants...>::type>;
> +	  remove_reference_t<_Nth_type_t<sizeof...(__indices), _Variants...>>;
>        using _Array_type =
>  	  _Multi_array<_Result_type (*)(_Visitor, _Variants...),
>  		       __dimensions...>;
> @@ -1374,7 +1372,7 @@ namespace __variant
>  	  = __detail::__variant::__accepted_index<_Tp, variant>;
>  
>        template<size_t _Np, typename = enable_if_t<(_Np < sizeof...(_Types))>>
> -	using __to_type = typename _Nth_type<_Np, _Types...>::type;
> +	using __to_type = _Nth_type_t<_Np, _Types...>;
>  
>        template<typename _Tp, typename = enable_if_t<__not_self<_Tp>>>
>  	using __accepted_type = __to_type<__accepted_index<_Tp>>;
> @@ -1511,7 +1509,7 @@ namespace __variant
>  	emplace(_Args&&... __args)
>  	{
>  	  namespace __variant = std::__detail::__variant;
> -	  using type = typename _Nth_type<_Np, _Types...>::type;
> +	  using type = _Nth_type_t<_Np, _Types...>;
>  	  // Provide the strong exception-safety guarantee when possible,
>  	  // to avoid becoming valueless.
>  	  if constexpr (is_nothrow_constructible_v<type, _Args...>)
> @@ -1551,7 +1549,7 @@ namespace __variant
>  	emplace(initializer_list<_Up> __il, _Args&&... __args)
>  	{
>  	  namespace __variant = std::__detail::__variant;
> -	  using type = typename _Nth_type<_Np, _Types...>::type;
> +	  using type = _Nth_type_t<_Np, _Types...>;
>  	  // Provide the strong exception-safety guarantee when possible,
>  	  // to avoid becoming valueless.
>  	  if constexpr (is_nothrow_constructible_v<type,
> @@ -1734,7 +1732,7 @@ namespace __variant
>  	  constexpr size_t __max = 11; // "These go to eleven."
>  
>  	  // The type of the first variant in the pack.
> -	  using _V0 = typename _Nth_type<0, _Variants...>::type;
> +	  using _V0 = _Nth_type_t<0, _Variants...>;
>  	  // The number of alternatives in that first variant.
>  	  constexpr auto __n = variant_size_v<remove_reference_t<_V0>>;
>  
> diff --git a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> index 84e085ebfbf..6279e24ba79 100644
> --- a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> +++ b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> @@ -61,3 +61,4 @@ test03()
>  
>  // { dg-error "tuple index must be in range" "" { target *-*-* } 0 }
>  // { dg-prune-output "no type named 'type' in .*_Nth_type" }
> +// { dg-prune-output "'__builtin_type_pack_element' index is out of range" }
> -- 
> 2.36.1
> 
> 


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2022-10-05 13:43 ` Patrick Palka
@ 2023-01-09 15:53   ` Patrick Palka
  2023-01-09 19:25     ` Patrick Palka
  0 siblings, 1 reply; 14+ messages in thread
From: Patrick Palka @ 2023-01-09 15:53 UTC (permalink / raw)
  To: Patrick Palka; +Cc: Jonathan Wakely, libstdc++, gcc-patches, Marek Polacek

On Wed, 5 Oct 2022, Patrick Palka wrote:

> On Thu, 7 Jul 2022, Jonathan Wakely via Gcc-patches wrote:
> 
> > This adds a new built-in to replace the recursive class template
> > instantiations done by traits such as std::tuple_element and
> > std::variant_alternative. The purpose is to select the Nth type from a
> > list of types, e.g. __builtin_type_pack_element(1, char, int, float) is
> > int.
> > 
> > For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> > the compilation time is reduced by more than 90% and the memory  used by
> > the compiler is reduced by 97%. In realistic examples the gains will be
> > much smaller, but still relevant.
> > 
> > Clang has a similar built-in, __type_pack_element<N, T...>, but that's a
> > "magic template" built-in using <> syntax, which GCC doesn't support. So
> > this provides an equivalent feature, but as a built-in function using
> > parens instead of <>. I don't really like the name "type pack element"
> > (it gives you an element from a pack of types) but the semi-consistency
> > with Clang seems like a reasonable argument in favour of keeping the
> > name. I'd be open to alternative names though, e.g. __builtin_nth_type
> > or __builtin_type_at_index.
> 
> Rather than giving the trait a different name from __type_pack_element,
> I wonder if we could just special case cp_parser_trait to expect <>
> instead of parens for this trait?
> 
> Btw the frontend recently got a generic TRAIT_TYPE tree code, which gets
> rid of much of the boilerplate of adding a new type-yielding built-in
> trait, see e.g. cp-trait.def.

Here's a tested patch based on Jonathan's original patch that implements
the built-in in terms of TRAIT_TYPE, names it __type_pack_element
instead of __builtin_type_pack_element, and treats invocations of it
like a template-id instead of a call (to match Clang).

-- >8 --

Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]

This adds a new built-in to replace the recursive class template
instantiations done by traits such as std::tuple_element and
std::variant_alternative.  The purpose is to select the Nth type from a
list of types, e.g. __type_pack_element<1, char, int, float> is int.
We implement it as a special kind of TRAIT_TYPE.

For a pathological example tuple_element_t<1000, tuple<2000 types...>>
the compilation time is reduced by more than 90% and the memory  used by
the compiler is reduced by 97%.  In realistic examples the gains will be
much smaller, but still relevant.

Unlike the other built-in traits, __type_pack_element uses template-id
syntax instead of call syntax and is SFINAE-enabled, matching Clang's
implementation.  And like the other built-in traits, it's not mangleable
so we can't use it directly in function signatures.

Some caveats:

  * Clang's version of the built-in seems to act like a "magic template"
    that can e.g. be used as a template template argument.  For simplicity
    we implement it in a more ad-hoc way.
  * Our parsing of the <>'s in __type_pack_element<...> is currently
    rudimentary and doesn't try to disambiguate a trailing >> vs > >
    as cp_parser_enclosed_template_argument_list does.

Co-authored-by: Jonathan Wakely <jwakely@redhat.com>

	PR c++/100157

gcc/cp/ChangeLog:

	* cp-trait.def (TYPE_PACK_ELEMENT): Define.
	* cp-tree.h (finish_trait_type): Add complain parameter.
	* cxx-pretty-print.cc (pp_cxx_trait): Handle
	CPTK_TYPE_PACK_ELEMENT.
	* parser.cc (cp_parser_constant_expression): Document default
	arguments.
	(cp_parser_trait): Handle CPTK_TYPE_PACK_ELEMENT.  Pass
	tf_warning_or_error to finish_trait_type.
	* pt.cc (tsubst) <case TRAIT_TYPE>: Handle
	CPTK_TYPE_PACK_ELEMENT.
	* semantics.cc (finish_type_pack_element): Define.
	(finish_trait_type): Add complain parameter.  Handle
	CPTK_TYPE_PACK_ELEMENT.
	* tree.cc (strip_typedefs): Pass tf_warning_or_error to
	finish_trait_type.
	* typeck.cc (structural_comptypes) <case TRAIT_TYPE>: Use
	cp_tree_equal instead of same_type_p for TRAIT_TYPE_TYPE.

libstdc++-v3/ChangeLog:

	* include/bits/utility.h (_Nth_type): Conditionally define using
	__type_pack_element.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/type_pack_element1.C: New test.
	* g++.dg/ext/type_pack_element2.C: New test.
	* g++.dg/ext/type_pack_element3.C: New test.
---
 gcc/cp/cp-trait.def                           |  1 +
 gcc/cp/cp-tree.h                              |  2 +-
 gcc/cp/cxx-pretty-print.cc                    | 17 ++++++--
 gcc/cp/parser.cc                              | 36 ++++++++++++-----
 gcc/cp/pt.cc                                  |  8 +++-
 gcc/cp/semantics.cc                           | 39 ++++++++++++++++++-
 gcc/cp/tree.cc                                |  3 +-
 gcc/cp/typeck.cc                              |  2 +-
 gcc/testsuite/g++.dg/ext/type_pack_element1.C | 19 +++++++++
 gcc/testsuite/g++.dg/ext/type_pack_element2.C | 14 +++++++
 gcc/testsuite/g++.dg/ext/type_pack_element3.C | 14 +++++++
 libstdc++-v3/include/bits/utility.h           |  6 +++
 12 files changed, 142 insertions(+), 19 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element1.C
 create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element2.C
 create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element3.C

diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
index 823899a26c5..63f6b101eeb 100644
--- a/gcc/cp/cp-trait.def
+++ b/gcc/cp/cp-trait.def
@@ -89,6 +89,7 @@ DEFTRAIT_TYPE (REMOVE_CV, "__remove_cv", 1)
 DEFTRAIT_TYPE (REMOVE_REFERENCE, "__remove_reference", 1)
 DEFTRAIT_TYPE (REMOVE_CVREF, "__remove_cvref", 1)
 DEFTRAIT_TYPE (UNDERLYING_TYPE,  "__underlying_type", 1)
+DEFTRAIT_TYPE (TYPE_PACK_ELEMENT, "__type_pack_element", -1)
 
 /* These traits yield a type pack, not a type, and are represented by
    cp_parser_trait as a special BASES tree instead of a TRAIT_TYPE tree.  */
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 1f4967c2ba0..f3cc2c5874c 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7764,7 +7764,7 @@ extern tree finish_decltype_type                (tree, bool, tsubst_flags_t);
 extern tree fold_builtin_is_corresponding_member (location_t, int, tree *);
 extern tree fold_builtin_is_pointer_inverconvertible_with_class (location_t, int, tree *);
 extern tree finish_trait_expr			(location_t, enum cp_trait_kind, tree, tree);
-extern tree finish_trait_type			(enum cp_trait_kind, tree, tree);
+extern tree finish_trait_type			(enum cp_trait_kind, tree, tree, tsubst_flags_t);
 extern tree build_lambda_expr                   (void);
 extern tree build_lambda_object			(tree);
 extern tree begin_lambda_type                   (tree);
diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
index 8ca1b8f234a..07a0406e491 100644
--- a/gcc/cp/cxx-pretty-print.cc
+++ b/gcc/cp/cxx-pretty-print.cc
@@ -2625,8 +2625,16 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
 #undef DEFTRAIT
     }
 
-  pp_cxx_left_paren (pp);
-  pp->type_id (type1);
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    {
+      pp_cxx_begin_template_argument_list (pp);
+      pp->expression (type1);
+    }
+  else
+    {
+      pp_cxx_left_paren (pp);
+      pp->type_id (type1);
+    }
   if (type2)
     {
       if (TREE_CODE (type2) != TREE_LIST)
@@ -2641,7 +2649,10 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
 	    pp->type_id (TREE_VALUE (arg));
 	  }
     }
-  pp_cxx_right_paren (pp);
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    pp_cxx_end_template_argument_list (pp);
+  else
+    pp_cxx_right_paren (pp);
 }
 
 // requires-clause:
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 8b1658decba..c6e31aa6f3f 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -10683,9 +10683,9 @@ cp_parser_expression (cp_parser* parser, cp_id_kind * pidk,
 
 static cp_expr
 cp_parser_constant_expression (cp_parser* parser,
-			       int allow_non_constant_p,
-			       bool *non_constant_p,
-			       bool strict_p)
+			       int allow_non_constant_p /* = 0 */,
+			       bool *non_constant_p /* = NULL */,
+			       bool strict_p /* = false */)
 {
   bool saved_integral_constant_expression_p;
   bool saved_allow_non_integral_constant_expression_p;
@@ -10912,12 +10912,25 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
   cp_lexer_consume_token (parser->lexer);
 
   matching_parens parens;
-  parens.require_open (parser);
 
-  {
-    type_id_in_expr_sentinel s (parser);
-    type1 = cp_parser_type_id (parser);
-  }
+  /* Unlike the other built-in traits, __type_pack_element is unique in that
+     it takes an expression as its first argument and it uses template-id
+     syntax instead of function call syntax (for compatibility with Clang).
+     We special case these properties of __type_pack_element here and elsewhere.
+
+     TODO: Generalize the trait infrastructure so that __type_pack_element
+     is no longer a special case.  */
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    {
+      cp_parser_require (parser, CPP_LESS, RT_LESS);
+      type1 = cp_parser_constant_expression (parser);
+    }
+  else
+    {
+      parens.require_open (parser);
+      type_id_in_expr_sentinel s (parser);
+      type1 = cp_parser_type_id (parser);
+    }
 
   if (type1 == error_mark_node)
     return error_mark_node;
@@ -10953,7 +10966,10 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
     }
 
   location_t finish_loc = cp_lexer_peek_token (parser->lexer)->location;
-  parens.require_close (parser);
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    cp_parser_require (parser, CPP_GREATER, RT_GREATER);
+  else
+    parens.require_close (parser);
 
   /* Construct a location of the form:
        __is_trivially_copyable(_Tp)
@@ -10971,7 +10987,7 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
       return cp_expr (finish_bases (type1, true), trait_loc);
     default:
       if (type)
-	return finish_trait_type (kind, type1, type2);
+	return finish_trait_type (kind, type1, type2, tf_warning_or_error);
       else
 	return finish_trait_expr (trait_loc, kind, type1, type2);
     }
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index cbe5898b553..2e83970a252 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -16576,9 +16576,13 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 
     case TRAIT_TYPE:
       {
-	tree type1 = tsubst (TRAIT_TYPE_TYPE1 (t), args, complain, in_decl);
+	tree type1 = TRAIT_TYPE_TYPE1 (t);
+	if (TRAIT_TYPE_KIND (t) != CPTK_TYPE_PACK_ELEMENT)
+	  type1 = tsubst (type1, args, complain, in_decl);
+	else
+	  type1 = tsubst_copy_and_build (type1, args, complain, in_decl);
 	tree type2 = tsubst (TRAIT_TYPE_TYPE2 (t), args, complain, in_decl);
-	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
+	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2, complain);
 	return cp_build_qualified_type (type,
 					cp_type_quals (t) | cp_type_quals (type),
 					complain | tf_ignore_bad_quals);
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index ef5bf2430b1..af9edf59b3b 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -4457,6 +4457,36 @@ finish_underlying_type (tree type)
   return underlying_type;
 }
 
+/* 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)
+{
+  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");
+      return error_mark_node;
+    }
+  HOST_WIDE_INT val = tree_to_shwi (idx);
+  if (val < 0)
+    {
+      if (complain & tf_error)
+	error ("%<__type_pack_element%> index is negative");
+      return error_mark_node;
+    }
+  tree result = chain_index (val, types);
+  if (!result)
+    {
+      if (complain & tf_error)
+	error ("%<__type_pack_element%> index is out of range");
+      return error_mark_node;
+    }
+  return TREE_VALUE (result);
+}
+
 /* Implement the __direct_bases keyword: Return the direct base classes
    of type.  */
 
@@ -12213,7 +12243,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
 /* Process a trait type.  */
 
 tree
-finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
+finish_trait_type (cp_trait_kind kind, tree type1, tree type2,
+		   tsubst_flags_t complain)
 {
   if (type1 == error_mark_node
       || type2 == error_mark_node)
@@ -12237,17 +12268,23 @@ finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
     {
     case CPTK_UNDERLYING_TYPE:
       return finish_underlying_type (type1);
+
     case CPTK_REMOVE_CV:
       return cv_unqualified (type1);
+
     case CPTK_REMOVE_REFERENCE:
       if (TYPE_REF_P (type1))
 	type1 = TREE_TYPE (type1);
       return type1;
+
     case CPTK_REMOVE_CVREF:
       if (TYPE_REF_P (type1))
 	type1 = TREE_TYPE (type1);
       return cv_unqualified (type1);
 
+    case CPTK_TYPE_PACK_ELEMENT:
+      return finish_type_pack_element (type1, type2, complain);
+
 #define DEFTRAIT_EXPR(CODE, NAME, ARITY) \
     case CPTK_##CODE:
 #include "cp-trait.def"
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index faf01616f87..dd5b0662235 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -1799,7 +1799,8 @@ strip_typedefs (tree t, bool *remove_attributes /* = NULL */,
 	if (type1 == TRAIT_TYPE_TYPE1 (t) && type2 == TRAIT_TYPE_TYPE2 (t))
 	  result = NULL_TREE;
 	else
-	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
+	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2,
+				      tf_warning_or_error);
       }
       break;
     case TYPE_PACK_EXPANSION:
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 69b1268cfec..f5548737e4c 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -1632,7 +1632,7 @@ structural_comptypes (tree t1, tree t2, int strict)
     case TRAIT_TYPE:
       if (TRAIT_TYPE_KIND (t1) != TRAIT_TYPE_KIND (t2))
 	return false;
-      if (!same_type_p (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
+      if (!cp_tree_equal (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
 	  || !cp_tree_equal (TRAIT_TYPE_TYPE2 (t1), TRAIT_TYPE_TYPE2 (t2)))
 	return false;
       break;
diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element1.C b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
new file mode 100644
index 00000000000..46858555502
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
@@ -0,0 +1,19 @@
+// { dg-do compile { target c++11 } }
+
+using ty0 = __type_pack_element<0, int>;
+using ty0 = __type_pack_element<0, int, char>;
+using ty0 = int;
+
+using ty1 = __type_pack_element<1, int, char>;
+using ty1 = __type_pack_element<(6 - 5) * 1, int, char>;
+using ty1 = char;
+
+template<int N, class... Ts>
+using __const_type_pack_element_t = const __type_pack_element<N, Ts...>;
+
+using ty2 = __const_type_pack_element_t<2, int, char, long>;
+using ty2 = const long;
+
+template<class T> struct A { };
+using ty3 = __type_pack_element<3, int, int, int, A<int>>;
+using ty3 = A<int>;
diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element2.C b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
new file mode 100644
index 00000000000..363cfa92514
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
@@ -0,0 +1,14 @@
+// { dg-do compile { target c++11 } }
+
+int p;
+
+using type = __type_pack_element<&p, int>; // { dg-error "not an integral constant" }
+using type = __type_pack_element<1, int>; // { dg-error "out of range" }
+using type = __type_pack_element<-1, int>; // { dg-error "negative" }
+using type = __type_pack_element<2, int, char>; // { dg-error "out of range" }
+
+template<int N, class... Ts>
+using __type_pack_element_t = __type_pack_element<N, Ts...>;
+// { dg-error "out of range" "" { target *-*-* } .-1 }
+
+using type = __type_pack_element_t<3, int, char, long>; // { dg-message "here" }
diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element3.C b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
new file mode 100644
index 00000000000..5cba03d4b81
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
@@ -0,0 +1,14 @@
+// { dg-do compile { target c++11 } }
+
+template<class T, T N, class... Ts, class = __type_pack_element<N, Ts...> >
+constexpr int f(int) { return 1; }
+
+template<class T, T N, class... Ts>
+constexpr int f(...) { return 2; };
+
+int p;
+
+static_assert(f<int, 0, void, char>(0) == 1, "");
+static_assert(f<int, 1, void, char>(0) == 1, "");
+static_assert(f<int, 2, void, char>(0) == 2, "");
+static_assert(f<int*, &p, void, char>(0) == 2, "");
diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
index 6a192e27836..5d524d33b4c 100644
--- a/libstdc++-v3/include/bits/utility.h
+++ b/libstdc++-v3/include/bits/utility.h
@@ -224,6 +224,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif // C++17
 #endif // C++14
 
+#if __has_builtin(__type_pack_element)
+  template<size_t _Np, typename... _Types>
+    struct _Nth_type
+    { using type = __type_pack_element<_Np, _Types...>; };
+#else
   template<size_t _Np, typename... _Types>
     struct _Nth_type
     { };
@@ -262,6 +267,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     struct _Nth_type<1, _Tp0, _Tp1, _Tp2, _Rest...>
     { using type = _Tp1; };
 #endif
+#endif
 
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace
-- 
2.39.0.198.ga38d39a4c5


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2023-01-09 15:53   ` Patrick Palka
@ 2023-01-09 19:25     ` Patrick Palka
  2023-01-10 10:17       ` Jonathan Wakely
  2023-01-17 18:04       ` Jason Merrill
  0 siblings, 2 replies; 14+ messages in thread
From: Patrick Palka @ 2023-01-09 19:25 UTC (permalink / raw)
  To: Patrick Palka; +Cc: Jonathan Wakely, libstdc++, gcc-patches, Marek Polacek

On Mon, 9 Jan 2023, Patrick Palka wrote:

> On Wed, 5 Oct 2022, Patrick Palka wrote:
> 
> > On Thu, 7 Jul 2022, Jonathan Wakely via Gcc-patches wrote:
> > 
> > > This adds a new built-in to replace the recursive class template
> > > instantiations done by traits such as std::tuple_element and
> > > std::variant_alternative. The purpose is to select the Nth type from a
> > > list of types, e.g. __builtin_type_pack_element(1, char, int, float) is
> > > int.
> > > 
> > > For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> > > the compilation time is reduced by more than 90% and the memory  used by
> > > the compiler is reduced by 97%. In realistic examples the gains will be
> > > much smaller, but still relevant.
> > > 
> > > Clang has a similar built-in, __type_pack_element<N, T...>, but that's a
> > > "magic template" built-in using <> syntax, which GCC doesn't support. So
> > > this provides an equivalent feature, but as a built-in function using
> > > parens instead of <>. I don't really like the name "type pack element"
> > > (it gives you an element from a pack of types) but the semi-consistency
> > > with Clang seems like a reasonable argument in favour of keeping the
> > > name. I'd be open to alternative names though, e.g. __builtin_nth_type
> > > or __builtin_type_at_index.
> > 
> > Rather than giving the trait a different name from __type_pack_element,
> > I wonder if we could just special case cp_parser_trait to expect <>
> > instead of parens for this trait?
> > 
> > Btw the frontend recently got a generic TRAIT_TYPE tree code, which gets
> > rid of much of the boilerplate of adding a new type-yielding built-in
> > trait, see e.g. cp-trait.def.
> 
> Here's a tested patch based on Jonathan's original patch that implements
> the built-in in terms of TRAIT_TYPE, names it __type_pack_element
> instead of __builtin_type_pack_element, and treats invocations of it
> like a template-id instead of a call (to match Clang).
> 
> -- >8 --
> 
> Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
> 
> This adds a new built-in to replace the recursive class template
> instantiations done by traits such as std::tuple_element and
> std::variant_alternative.  The purpose is to select the Nth type from a
> list of types, e.g. __type_pack_element<1, char, int, float> is int.
> We implement it as a special kind of TRAIT_TYPE.
> 
> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> the compilation time is reduced by more than 90% and the memory  used by
> the compiler is reduced by 97%.  In realistic examples the gains will be
> much smaller, but still relevant.
> 
> Unlike the other built-in traits, __type_pack_element uses template-id
> syntax instead of call syntax and is SFINAE-enabled, matching Clang's
> implementation.  And like the other built-in traits, it's not mangleable
> so we can't use it directly in function signatures.
> 
> Some caveats:
> 
>   * Clang's version of the built-in seems to act like a "magic template"
>     that can e.g. be used as a template template argument.  For simplicity
>     we implement it in a more ad-hoc way.
>   * Our parsing of the <>'s in __type_pack_element<...> is currently
>     rudimentary and doesn't try to disambiguate a trailing >> vs > >
>     as cp_parser_enclosed_template_argument_list does.

Hmm, this latter caveat turns out to be inconvenient (for code such as
type_pack_element3.C) and admits an easy workaround inspired by what
cp_parser_enclosed_template_argument_list does.

v2: Consider the >> in __type_pack_element<0, int, char>> to be two >'s.
    Handle non-type TRAIT_TYPE_TYPE1 in strip_typedefs (for sake of
    CPTK_TYPE_PACK_ELEMENT).

-- >8 --

Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]

This adds a new built-in to replace the recursive class template
instantiations done by traits such as std::tuple_element and
std::variant_alternative.  The purpose is to select the Nth type from a
list of types, e.g. __type_pack_element<1, char, int, float> is int.
We implement it as a special kind of TRAIT_TYPE.

For a pathological example tuple_element_t<1000, tuple<2000 types...>>
the compilation time is reduced by more than 90% and the memory  used by
the compiler is reduced by 97%.  In realistic examples the gains will be
much smaller, but still relevant.

Unlike the other built-in traits, __type_pack_element uses template-id
syntax instead of call syntax and is SFINAE-enabled, matching Clang's
implementation.  And like the other built-in traits, it's not mangleable
so we can't use it directly in function signatures.

N.B. Clang seems to implement __type_pack_element as a first-class
template that can e.g. be used as a template template argument.  For
simplicity we implement it in a more ad-hoc way.

Co-authored-by: Jonathan Wakely <jwakely@redhat.com>

	PR c++/100157

gcc/cp/ChangeLog:

	* cp-trait.def (TYPE_PACK_ELEMENT): Define.
	* cp-tree.h (finish_trait_type): Add complain parameter.
	* cxx-pretty-print.cc (pp_cxx_trait): Handle
	CPTK_TYPE_PACK_ELEMENT.
	* parser.cc (cp_parser_constant_expression): Document default
	arguments.
	(cp_parser_trait): Handle CPTK_TYPE_PACK_ELEMENT.  Pass
	tf_warning_or_error to finish_trait_type.
	* pt.cc (tsubst) <case TRAIT_TYPE>: Handle
	CPTK_TYPE_PACK_ELEMENT.
	* semantics.cc (finish_type_pack_element): Define.
	(finish_trait_type): Add complain parameter.  Handle
	CPTK_TYPE_PACK_ELEMENT.
	* tree.cc (strip_typedefs): Handle non-type TRAIT_TYPE_TYPE1.
	Pass tf_warning_or_error to finish_trait_type.
	* typeck.cc (structural_comptypes) <case TRAIT_TYPE>: Use
	cp_tree_equal instead of same_type_p for TRAIT_TYPE_TYPE1.

libstdc++-v3/ChangeLog:

	* include/bits/utility.h (_Nth_type): Conditionally define in
	terms of __type_pack_element if available.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/type_pack_element1.C: New test.
	* g++.dg/ext/type_pack_element2.C: New test.
	* g++.dg/ext/type_pack_element3.C: New test.
---
 gcc/cp/cp-trait.def                           |  1 +
 gcc/cp/cp-tree.h                              |  2 +-
 gcc/cp/cxx-pretty-print.cc                    | 17 +++++--
 gcc/cp/parser.cc                              | 46 +++++++++++++++----
 gcc/cp/pt.cc                                  |  8 +++-
 gcc/cp/semantics.cc                           | 39 +++++++++++++++-
 gcc/cp/tree.cc                                | 10 ++--
 gcc/cp/typeck.cc                              |  2 +-
 gcc/testsuite/g++.dg/ext/type_pack_element1.C | 19 ++++++++
 gcc/testsuite/g++.dg/ext/type_pack_element2.C | 14 ++++++
 gcc/testsuite/g++.dg/ext/type_pack_element3.C | 22 +++++++++
 libstdc++-v3/include/bits/utility.h           |  6 +++
 12 files changed, 165 insertions(+), 21 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element1.C
 create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element2.C
 create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element3.C

diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
index 823899a26c5..63f6b101eeb 100644
--- a/gcc/cp/cp-trait.def
+++ b/gcc/cp/cp-trait.def
@@ -89,6 +89,7 @@ DEFTRAIT_TYPE (REMOVE_CV, "__remove_cv", 1)
 DEFTRAIT_TYPE (REMOVE_REFERENCE, "__remove_reference", 1)
 DEFTRAIT_TYPE (REMOVE_CVREF, "__remove_cvref", 1)
 DEFTRAIT_TYPE (UNDERLYING_TYPE,  "__underlying_type", 1)
+DEFTRAIT_TYPE (TYPE_PACK_ELEMENT, "__type_pack_element", -1)
 
 /* These traits yield a type pack, not a type, and are represented by
    cp_parser_trait as a special BASES tree instead of a TRAIT_TYPE tree.  */
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 1f4967c2ba0..f3cc2c5874c 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7764,7 +7764,7 @@ extern tree finish_decltype_type                (tree, bool, tsubst_flags_t);
 extern tree fold_builtin_is_corresponding_member (location_t, int, tree *);
 extern tree fold_builtin_is_pointer_inverconvertible_with_class (location_t, int, tree *);
 extern tree finish_trait_expr			(location_t, enum cp_trait_kind, tree, tree);
-extern tree finish_trait_type			(enum cp_trait_kind, tree, tree);
+extern tree finish_trait_type			(enum cp_trait_kind, tree, tree, tsubst_flags_t);
 extern tree build_lambda_expr                   (void);
 extern tree build_lambda_object			(tree);
 extern tree begin_lambda_type                   (tree);
diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
index 8ca1b8f234a..07a0406e491 100644
--- a/gcc/cp/cxx-pretty-print.cc
+++ b/gcc/cp/cxx-pretty-print.cc
@@ -2625,8 +2625,16 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
 #undef DEFTRAIT
     }
 
-  pp_cxx_left_paren (pp);
-  pp->type_id (type1);
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    {
+      pp_cxx_begin_template_argument_list (pp);
+      pp->expression (type1);
+    }
+  else
+    {
+      pp_cxx_left_paren (pp);
+      pp->type_id (type1);
+    }
   if (type2)
     {
       if (TREE_CODE (type2) != TREE_LIST)
@@ -2641,7 +2649,10 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
 	    pp->type_id (TREE_VALUE (arg));
 	  }
     }
-  pp_cxx_right_paren (pp);
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    pp_cxx_end_template_argument_list (pp);
+  else
+    pp_cxx_right_paren (pp);
 }
 
 // requires-clause:
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 8b1658decba..0a88d83d955 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -10683,9 +10683,9 @@ cp_parser_expression (cp_parser* parser, cp_id_kind * pidk,
 
 static cp_expr
 cp_parser_constant_expression (cp_parser* parser,
-			       int allow_non_constant_p,
-			       bool *non_constant_p,
-			       bool strict_p)
+			       int allow_non_constant_p /* = 0 */,
+			       bool *non_constant_p /* = NULL */,
+			       bool strict_p /* = false */)
 {
   bool saved_integral_constant_expression_p;
   bool saved_allow_non_integral_constant_expression_p;
@@ -10912,12 +10912,25 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
   cp_lexer_consume_token (parser->lexer);
 
   matching_parens parens;
-  parens.require_open (parser);
 
-  {
-    type_id_in_expr_sentinel s (parser);
-    type1 = cp_parser_type_id (parser);
-  }
+  /* Unlike the other built-in traits, __type_pack_element is unique in that
+     it takes an expression as its first argument and it uses template-id
+     syntax instead of function call syntax (for compatibility with Clang).
+     We special case these properties of __type_pack_element here and elsewhere.
+
+     TODO: Generalize the trait infrastructure so that __type_pack_element
+     is no longer a special case.  */
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    {
+      cp_parser_require (parser, CPP_LESS, RT_LESS);
+      type1 = cp_parser_constant_expression (parser);
+    }
+  else
+    {
+      parens.require_open (parser);
+      type_id_in_expr_sentinel s (parser);
+      type1 = cp_parser_type_id (parser);
+    }
 
   if (type1 == error_mark_node)
     return error_mark_node;
@@ -10953,7 +10966,20 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
     }
 
   location_t finish_loc = cp_lexer_peek_token (parser->lexer)->location;
-  parens.require_close (parser);
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    {
+      /* As in cp_parser_enclosed_template_argument_list, a trailing
+	 '>>' here is considered to be two separate '>' tokens.  */
+      if (cp_lexer_next_token_is (parser->lexer, CPP_RSHIFT))
+	{
+	  cp_token *token = cp_lexer_peek_token (parser->lexer);
+	  token->type = CPP_GREATER;
+	}
+      else
+	cp_parser_require (parser, CPP_GREATER, RT_GREATER);
+    }
+  else
+    parens.require_close (parser);
 
   /* Construct a location of the form:
        __is_trivially_copyable(_Tp)
@@ -10971,7 +10997,7 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
       return cp_expr (finish_bases (type1, true), trait_loc);
     default:
       if (type)
-	return finish_trait_type (kind, type1, type2);
+	return finish_trait_type (kind, type1, type2, tf_warning_or_error);
       else
 	return finish_trait_expr (trait_loc, kind, type1, type2);
     }
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index cbe5898b553..2e83970a252 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -16576,9 +16576,13 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 
     case TRAIT_TYPE:
       {
-	tree type1 = tsubst (TRAIT_TYPE_TYPE1 (t), args, complain, in_decl);
+	tree type1 = TRAIT_TYPE_TYPE1 (t);
+	if (TRAIT_TYPE_KIND (t) != CPTK_TYPE_PACK_ELEMENT)
+	  type1 = tsubst (type1, args, complain, in_decl);
+	else
+	  type1 = tsubst_copy_and_build (type1, args, complain, in_decl);
 	tree type2 = tsubst (TRAIT_TYPE_TYPE2 (t), args, complain, in_decl);
-	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
+	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2, complain);
 	return cp_build_qualified_type (type,
 					cp_type_quals (t) | cp_type_quals (type),
 					complain | tf_ignore_bad_quals);
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index ef5bf2430b1..af9edf59b3b 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -4457,6 +4457,36 @@ finish_underlying_type (tree type)
   return underlying_type;
 }
 
+/* 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)
+{
+  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");
+      return error_mark_node;
+    }
+  HOST_WIDE_INT val = tree_to_shwi (idx);
+  if (val < 0)
+    {
+      if (complain & tf_error)
+	error ("%<__type_pack_element%> index is negative");
+      return error_mark_node;
+    }
+  tree result = chain_index (val, types);
+  if (!result)
+    {
+      if (complain & tf_error)
+	error ("%<__type_pack_element%> index is out of range");
+      return error_mark_node;
+    }
+  return TREE_VALUE (result);
+}
+
 /* Implement the __direct_bases keyword: Return the direct base classes
    of type.  */
 
@@ -12213,7 +12243,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
 /* Process a trait type.  */
 
 tree
-finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
+finish_trait_type (cp_trait_kind kind, tree type1, tree type2,
+		   tsubst_flags_t complain)
 {
   if (type1 == error_mark_node
       || type2 == error_mark_node)
@@ -12237,17 +12268,23 @@ finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
     {
     case CPTK_UNDERLYING_TYPE:
       return finish_underlying_type (type1);
+
     case CPTK_REMOVE_CV:
       return cv_unqualified (type1);
+
     case CPTK_REMOVE_REFERENCE:
       if (TYPE_REF_P (type1))
 	type1 = TREE_TYPE (type1);
       return type1;
+
     case CPTK_REMOVE_CVREF:
       if (TYPE_REF_P (type1))
 	type1 = TREE_TYPE (type1);
       return cv_unqualified (type1);
 
+    case CPTK_TYPE_PACK_ELEMENT:
+      return finish_type_pack_element (type1, type2, complain);
+
 #define DEFTRAIT_EXPR(CODE, NAME, ARITY) \
     case CPTK_##CODE:
 #include "cp-trait.def"
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index faf01616f87..d834a0fd38b 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -1792,14 +1792,18 @@ strip_typedefs (tree t, bool *remove_attributes /* = NULL */,
       break;
     case TRAIT_TYPE:
       {
-	tree type1 = strip_typedefs (TRAIT_TYPE_TYPE1 (t),
-				     remove_attributes, flags);
+	tree type1 = TRAIT_TYPE_TYPE1 (t);
+	if (TYPE_P (type1))
+	  type1 = strip_typedefs (type1, remove_attributes, flags);
+	else
+	  type1 = strip_typedefs_expr (type1, remove_attributes, flags);
 	tree type2 = strip_typedefs (TRAIT_TYPE_TYPE2 (t),
 				     remove_attributes, flags);
 	if (type1 == TRAIT_TYPE_TYPE1 (t) && type2 == TRAIT_TYPE_TYPE2 (t))
 	  result = NULL_TREE;
 	else
-	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
+	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2,
+				      tf_warning_or_error);
       }
       break;
     case TYPE_PACK_EXPANSION:
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 69b1268cfec..f5548737e4c 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -1632,7 +1632,7 @@ structural_comptypes (tree t1, tree t2, int strict)
     case TRAIT_TYPE:
       if (TRAIT_TYPE_KIND (t1) != TRAIT_TYPE_KIND (t2))
 	return false;
-      if (!same_type_p (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
+      if (!cp_tree_equal (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
 	  || !cp_tree_equal (TRAIT_TYPE_TYPE2 (t1), TRAIT_TYPE_TYPE2 (t2)))
 	return false;
       break;
diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element1.C b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
new file mode 100644
index 00000000000..46858555502
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
@@ -0,0 +1,19 @@
+// { dg-do compile { target c++11 } }
+
+using ty0 = __type_pack_element<0, int>;
+using ty0 = __type_pack_element<0, int, char>;
+using ty0 = int;
+
+using ty1 = __type_pack_element<1, int, char>;
+using ty1 = __type_pack_element<(6 - 5) * 1, int, char>;
+using ty1 = char;
+
+template<int N, class... Ts>
+using __const_type_pack_element_t = const __type_pack_element<N, Ts...>;
+
+using ty2 = __const_type_pack_element_t<2, int, char, long>;
+using ty2 = const long;
+
+template<class T> struct A { };
+using ty3 = __type_pack_element<3, int, int, int, A<int>>;
+using ty3 = A<int>;
diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element2.C b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
new file mode 100644
index 00000000000..363cfa92514
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
@@ -0,0 +1,14 @@
+// { dg-do compile { target c++11 } }
+
+int p;
+
+using type = __type_pack_element<&p, int>; // { dg-error "not an integral constant" }
+using type = __type_pack_element<1, int>; // { dg-error "out of range" }
+using type = __type_pack_element<-1, int>; // { dg-error "negative" }
+using type = __type_pack_element<2, int, char>; // { dg-error "out of range" }
+
+template<int N, class... Ts>
+using __type_pack_element_t = __type_pack_element<N, Ts...>;
+// { dg-error "out of range" "" { target *-*-* } .-1 }
+
+using type = __type_pack_element_t<3, int, char, long>; // { dg-message "here" }
diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element3.C b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
new file mode 100644
index 00000000000..269f84f464f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
@@ -0,0 +1,22 @@
+// { dg-do compile { target c++11 } }
+
+template<class T, T N, class... Ts, class = __type_pack_element<N, Ts...>>
+constexpr int f(int) { return 1; }
+
+template<class T, T N, class... Ts>
+constexpr int f(...) { return 2; };
+
+int p;
+
+static_assert(f<int, 0, void, char>(0) == 1, "");
+static_assert(f<int, 1, void, char>(0) == 1, "");
+static_assert(f<int, 2, void, char>(0) == 2, "");
+static_assert(f<int*, &p, void, char>(0) == 2, "");
+
+template<class T, class U> struct A;
+template<class T> struct A<T, __type_pack_element<sizeof(T), void, long>> { };
+template struct A<char, long>;
+
+template<class T, class U> struct B;
+template<class T> struct B<T, __type_pack_element<0, T, short>> { };
+template struct B<int, int>;
diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
index 6a192e27836..5d524d33b4c 100644
--- a/libstdc++-v3/include/bits/utility.h
+++ b/libstdc++-v3/include/bits/utility.h
@@ -224,6 +224,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif // C++17
 #endif // C++14
 
+#if __has_builtin(__type_pack_element)
+  template<size_t _Np, typename... _Types>
+    struct _Nth_type
+    { using type = __type_pack_element<_Np, _Types...>; };
+#else
   template<size_t _Np, typename... _Types>
     struct _Nth_type
     { };
@@ -262,6 +267,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     struct _Nth_type<1, _Tp0, _Tp1, _Tp2, _Rest...>
     { using type = _Tp1; };
 #endif
+#endif
 
 _GLIBCXX_END_NAMESPACE_VERSION
 } // namespace
-- 
2.39.0.198.ga38d39a4c5


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2023-01-09 19:25     ` Patrick Palka
@ 2023-01-10 10:17       ` Jonathan Wakely
  2023-01-17 18:04       ` Jason Merrill
  1 sibling, 0 replies; 14+ messages in thread
From: Jonathan Wakely @ 2023-01-10 10:17 UTC (permalink / raw)
  To: Patrick Palka; +Cc: libstdc++, gcc-patches, Marek Polacek

On Mon, 9 Jan 2023 at 19:25, Patrick Palka wrote:
>
> On Mon, 9 Jan 2023, Patrick Palka wrote:
>
> > On Wed, 5 Oct 2022, Patrick Palka wrote:
> >
> > > On Thu, 7 Jul 2022, Jonathan Wakely via Gcc-patches wrote:
> > >
> > > > This adds a new built-in to replace the recursive class template
> > > > instantiations done by traits such as std::tuple_element and
> > > > std::variant_alternative. The purpose is to select the Nth type from a
> > > > list of types, e.g. __builtin_type_pack_element(1, char, int, float) is
> > > > int.
> > > >
> > > > For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> > > > the compilation time is reduced by more than 90% and the memory  used by
> > > > the compiler is reduced by 97%. In realistic examples the gains will be
> > > > much smaller, but still relevant.
> > > >
> > > > Clang has a similar built-in, __type_pack_element<N, T...>, but that's a
> > > > "magic template" built-in using <> syntax, which GCC doesn't support. So
> > > > this provides an equivalent feature, but as a built-in function using
> > > > parens instead of <>. I don't really like the name "type pack element"
> > > > (it gives you an element from a pack of types) but the semi-consistency
> > > > with Clang seems like a reasonable argument in favour of keeping the
> > > > name. I'd be open to alternative names though, e.g. __builtin_nth_type
> > > > or __builtin_type_at_index.
> > >
> > > Rather than giving the trait a different name from __type_pack_element,
> > > I wonder if we could just special case cp_parser_trait to expect <>
> > > instead of parens for this trait?
> > >
> > > Btw the frontend recently got a generic TRAIT_TYPE tree code, which gets
> > > rid of much of the boilerplate of adding a new type-yielding built-in
> > > trait, see e.g. cp-trait.def.
> >
> > Here's a tested patch based on Jonathan's original patch that implements
> > the built-in in terms of TRAIT_TYPE, names it __type_pack_element
> > instead of __builtin_type_pack_element, and treats invocations of it
> > like a template-id instead of a call (to match Clang).

The library change is very much OK, thanks for taking this over.


> >
> > -- >8 --
> >
> > Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
> >
> > This adds a new built-in to replace the recursive class template
> > instantiations done by traits such as std::tuple_element and
> > std::variant_alternative.  The purpose is to select the Nth type from a
> > list of types, e.g. __type_pack_element<1, char, int, float> is int.
> > We implement it as a special kind of TRAIT_TYPE.
> >
> > For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> > the compilation time is reduced by more than 90% and the memory  used by
> > the compiler is reduced by 97%.  In realistic examples the gains will be
> > much smaller, but still relevant.
> >
> > Unlike the other built-in traits, __type_pack_element uses template-id
> > syntax instead of call syntax and is SFINAE-enabled, matching Clang's
> > implementation.  And like the other built-in traits, it's not mangleable
> > so we can't use it directly in function signatures.
> >
> > Some caveats:
> >
> >   * Clang's version of the built-in seems to act like a "magic template"
> >     that can e.g. be used as a template template argument.  For simplicity
> >     we implement it in a more ad-hoc way.
> >   * Our parsing of the <>'s in __type_pack_element<...> is currently
> >     rudimentary and doesn't try to disambiguate a trailing >> vs > >
> >     as cp_parser_enclosed_template_argument_list does.
>
> Hmm, this latter caveat turns out to be inconvenient (for code such as
> type_pack_element3.C) and admits an easy workaround inspired by what
> cp_parser_enclosed_template_argument_list does.
>
> v2: Consider the >> in __type_pack_element<0, int, char>> to be two >'s.
>     Handle non-type TRAIT_TYPE_TYPE1 in strip_typedefs (for sake of
>     CPTK_TYPE_PACK_ELEMENT).
>
> -- >8 --
>
> Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
>
> This adds a new built-in to replace the recursive class template
> instantiations done by traits such as std::tuple_element and
> std::variant_alternative.  The purpose is to select the Nth type from a
> list of types, e.g. __type_pack_element<1, char, int, float> is int.
> We implement it as a special kind of TRAIT_TYPE.
>
> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> the compilation time is reduced by more than 90% and the memory  used by
> the compiler is reduced by 97%.  In realistic examples the gains will be
> much smaller, but still relevant.
>
> Unlike the other built-in traits, __type_pack_element uses template-id
> syntax instead of call syntax and is SFINAE-enabled, matching Clang's
> implementation.  And like the other built-in traits, it's not mangleable
> so we can't use it directly in function signatures.
>
> N.B. Clang seems to implement __type_pack_element as a first-class
> template that can e.g. be used as a template template argument.  For
> simplicity we implement it in a more ad-hoc way.
>
> Co-authored-by: Jonathan Wakely <jwakely@redhat.com>
>
>         PR c++/100157
>
> gcc/cp/ChangeLog:
>
>         * cp-trait.def (TYPE_PACK_ELEMENT): Define.
>         * cp-tree.h (finish_trait_type): Add complain parameter.
>         * cxx-pretty-print.cc (pp_cxx_trait): Handle
>         CPTK_TYPE_PACK_ELEMENT.
>         * parser.cc (cp_parser_constant_expression): Document default
>         arguments.
>         (cp_parser_trait): Handle CPTK_TYPE_PACK_ELEMENT.  Pass
>         tf_warning_or_error to finish_trait_type.
>         * pt.cc (tsubst) <case TRAIT_TYPE>: Handle
>         CPTK_TYPE_PACK_ELEMENT.
>         * semantics.cc (finish_type_pack_element): Define.
>         (finish_trait_type): Add complain parameter.  Handle
>         CPTK_TYPE_PACK_ELEMENT.
>         * tree.cc (strip_typedefs): Handle non-type TRAIT_TYPE_TYPE1.
>         Pass tf_warning_or_error to finish_trait_type.
>         * typeck.cc (structural_comptypes) <case TRAIT_TYPE>: Use
>         cp_tree_equal instead of same_type_p for TRAIT_TYPE_TYPE1.
>
> libstdc++-v3/ChangeLog:
>
>         * include/bits/utility.h (_Nth_type): Conditionally define in
>         terms of __type_pack_element if available.
>
> gcc/testsuite/ChangeLog:
>
>         * g++.dg/ext/type_pack_element1.C: New test.
>         * g++.dg/ext/type_pack_element2.C: New test.
>         * g++.dg/ext/type_pack_element3.C: New test.
> ---
>  gcc/cp/cp-trait.def                           |  1 +
>  gcc/cp/cp-tree.h                              |  2 +-
>  gcc/cp/cxx-pretty-print.cc                    | 17 +++++--
>  gcc/cp/parser.cc                              | 46 +++++++++++++++----
>  gcc/cp/pt.cc                                  |  8 +++-
>  gcc/cp/semantics.cc                           | 39 +++++++++++++++-
>  gcc/cp/tree.cc                                | 10 ++--
>  gcc/cp/typeck.cc                              |  2 +-
>  gcc/testsuite/g++.dg/ext/type_pack_element1.C | 19 ++++++++
>  gcc/testsuite/g++.dg/ext/type_pack_element2.C | 14 ++++++
>  gcc/testsuite/g++.dg/ext/type_pack_element3.C | 22 +++++++++
>  libstdc++-v3/include/bits/utility.h           |  6 +++
>  12 files changed, 165 insertions(+), 21 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element1.C
>  create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element2.C
>  create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element3.C
>
> diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
> index 823899a26c5..63f6b101eeb 100644
> --- a/gcc/cp/cp-trait.def
> +++ b/gcc/cp/cp-trait.def
> @@ -89,6 +89,7 @@ DEFTRAIT_TYPE (REMOVE_CV, "__remove_cv", 1)
>  DEFTRAIT_TYPE (REMOVE_REFERENCE, "__remove_reference", 1)
>  DEFTRAIT_TYPE (REMOVE_CVREF, "__remove_cvref", 1)
>  DEFTRAIT_TYPE (UNDERLYING_TYPE,  "__underlying_type", 1)
> +DEFTRAIT_TYPE (TYPE_PACK_ELEMENT, "__type_pack_element", -1)
>
>  /* These traits yield a type pack, not a type, and are represented by
>     cp_parser_trait as a special BASES tree instead of a TRAIT_TYPE tree.  */
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 1f4967c2ba0..f3cc2c5874c 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7764,7 +7764,7 @@ extern tree finish_decltype_type                (tree, bool, tsubst_flags_t);
>  extern tree fold_builtin_is_corresponding_member (location_t, int, tree *);
>  extern tree fold_builtin_is_pointer_inverconvertible_with_class (location_t, int, tree *);
>  extern tree finish_trait_expr                  (location_t, enum cp_trait_kind, tree, tree);
> -extern tree finish_trait_type                  (enum cp_trait_kind, tree, tree);
> +extern tree finish_trait_type                  (enum cp_trait_kind, tree, tree, tsubst_flags_t);
>  extern tree build_lambda_expr                   (void);
>  extern tree build_lambda_object                        (tree);
>  extern tree begin_lambda_type                   (tree);
> diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
> index 8ca1b8f234a..07a0406e491 100644
> --- a/gcc/cp/cxx-pretty-print.cc
> +++ b/gcc/cp/cxx-pretty-print.cc
> @@ -2625,8 +2625,16 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
>  #undef DEFTRAIT
>      }
>
> -  pp_cxx_left_paren (pp);
> -  pp->type_id (type1);
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    {
> +      pp_cxx_begin_template_argument_list (pp);
> +      pp->expression (type1);
> +    }
> +  else
> +    {
> +      pp_cxx_left_paren (pp);
> +      pp->type_id (type1);
> +    }
>    if (type2)
>      {
>        if (TREE_CODE (type2) != TREE_LIST)
> @@ -2641,7 +2649,10 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
>             pp->type_id (TREE_VALUE (arg));
>           }
>      }
> -  pp_cxx_right_paren (pp);
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    pp_cxx_end_template_argument_list (pp);
> +  else
> +    pp_cxx_right_paren (pp);
>  }
>
>  // requires-clause:
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index 8b1658decba..0a88d83d955 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -10683,9 +10683,9 @@ cp_parser_expression (cp_parser* parser, cp_id_kind * pidk,
>
>  static cp_expr
>  cp_parser_constant_expression (cp_parser* parser,
> -                              int allow_non_constant_p,
> -                              bool *non_constant_p,
> -                              bool strict_p)
> +                              int allow_non_constant_p /* = 0 */,
> +                              bool *non_constant_p /* = NULL */,
> +                              bool strict_p /* = false */)
>  {
>    bool saved_integral_constant_expression_p;
>    bool saved_allow_non_integral_constant_expression_p;
> @@ -10912,12 +10912,25 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
>    cp_lexer_consume_token (parser->lexer);
>
>    matching_parens parens;
> -  parens.require_open (parser);
>
> -  {
> -    type_id_in_expr_sentinel s (parser);
> -    type1 = cp_parser_type_id (parser);
> -  }
> +  /* Unlike the other built-in traits, __type_pack_element is unique in that
> +     it takes an expression as its first argument and it uses template-id
> +     syntax instead of function call syntax (for compatibility with Clang).
> +     We special case these properties of __type_pack_element here and elsewhere.
> +
> +     TODO: Generalize the trait infrastructure so that __type_pack_element
> +     is no longer a special case.  */
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    {
> +      cp_parser_require (parser, CPP_LESS, RT_LESS);
> +      type1 = cp_parser_constant_expression (parser);
> +    }
> +  else
> +    {
> +      parens.require_open (parser);
> +      type_id_in_expr_sentinel s (parser);
> +      type1 = cp_parser_type_id (parser);
> +    }
>
>    if (type1 == error_mark_node)
>      return error_mark_node;
> @@ -10953,7 +10966,20 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
>      }
>
>    location_t finish_loc = cp_lexer_peek_token (parser->lexer)->location;
> -  parens.require_close (parser);
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    {
> +      /* As in cp_parser_enclosed_template_argument_list, a trailing
> +        '>>' here is considered to be two separate '>' tokens.  */
> +      if (cp_lexer_next_token_is (parser->lexer, CPP_RSHIFT))
> +       {
> +         cp_token *token = cp_lexer_peek_token (parser->lexer);
> +         token->type = CPP_GREATER;
> +       }
> +      else
> +       cp_parser_require (parser, CPP_GREATER, RT_GREATER);
> +    }
> +  else
> +    parens.require_close (parser);
>
>    /* Construct a location of the form:
>         __is_trivially_copyable(_Tp)
> @@ -10971,7 +10997,7 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
>        return cp_expr (finish_bases (type1, true), trait_loc);
>      default:
>        if (type)
> -       return finish_trait_type (kind, type1, type2);
> +       return finish_trait_type (kind, type1, type2, tf_warning_or_error);
>        else
>         return finish_trait_expr (trait_loc, kind, type1, type2);
>      }
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index cbe5898b553..2e83970a252 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -16576,9 +16576,13 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>
>      case TRAIT_TYPE:
>        {
> -       tree type1 = tsubst (TRAIT_TYPE_TYPE1 (t), args, complain, in_decl);
> +       tree type1 = TRAIT_TYPE_TYPE1 (t);
> +       if (TRAIT_TYPE_KIND (t) != CPTK_TYPE_PACK_ELEMENT)
> +         type1 = tsubst (type1, args, complain, in_decl);
> +       else
> +         type1 = tsubst_copy_and_build (type1, args, complain, in_decl);
>         tree type2 = tsubst (TRAIT_TYPE_TYPE2 (t), args, complain, in_decl);
> -       type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
> +       type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2, complain);
>         return cp_build_qualified_type (type,
>                                         cp_type_quals (t) | cp_type_quals (type),
>                                         complain | tf_ignore_bad_quals);
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index ef5bf2430b1..af9edf59b3b 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -4457,6 +4457,36 @@ finish_underlying_type (tree type)
>    return underlying_type;
>  }
>
> +/* 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)
> +{
> +  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");
> +      return error_mark_node;
> +    }
> +  HOST_WIDE_INT val = tree_to_shwi (idx);
> +  if (val < 0)
> +    {
> +      if (complain & tf_error)
> +       error ("%<__type_pack_element%> index is negative");
> +      return error_mark_node;
> +    }
> +  tree result = chain_index (val, types);
> +  if (!result)
> +    {
> +      if (complain & tf_error)
> +       error ("%<__type_pack_element%> index is out of range");
> +      return error_mark_node;
> +    }
> +  return TREE_VALUE (result);
> +}
> +
>  /* Implement the __direct_bases keyword: Return the direct base classes
>     of type.  */
>
> @@ -12213,7 +12243,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
>  /* Process a trait type.  */
>
>  tree
> -finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
> +finish_trait_type (cp_trait_kind kind, tree type1, tree type2,
> +                  tsubst_flags_t complain)
>  {
>    if (type1 == error_mark_node
>        || type2 == error_mark_node)
> @@ -12237,17 +12268,23 @@ finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
>      {
>      case CPTK_UNDERLYING_TYPE:
>        return finish_underlying_type (type1);
> +
>      case CPTK_REMOVE_CV:
>        return cv_unqualified (type1);
> +
>      case CPTK_REMOVE_REFERENCE:
>        if (TYPE_REF_P (type1))
>         type1 = TREE_TYPE (type1);
>        return type1;
> +
>      case CPTK_REMOVE_CVREF:
>        if (TYPE_REF_P (type1))
>         type1 = TREE_TYPE (type1);
>        return cv_unqualified (type1);
>
> +    case CPTK_TYPE_PACK_ELEMENT:
> +      return finish_type_pack_element (type1, type2, complain);
> +
>  #define DEFTRAIT_EXPR(CODE, NAME, ARITY) \
>      case CPTK_##CODE:
>  #include "cp-trait.def"
> diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> index faf01616f87..d834a0fd38b 100644
> --- a/gcc/cp/tree.cc
> +++ b/gcc/cp/tree.cc
> @@ -1792,14 +1792,18 @@ strip_typedefs (tree t, bool *remove_attributes /* = NULL */,
>        break;
>      case TRAIT_TYPE:
>        {
> -       tree type1 = strip_typedefs (TRAIT_TYPE_TYPE1 (t),
> -                                    remove_attributes, flags);
> +       tree type1 = TRAIT_TYPE_TYPE1 (t);
> +       if (TYPE_P (type1))
> +         type1 = strip_typedefs (type1, remove_attributes, flags);
> +       else
> +         type1 = strip_typedefs_expr (type1, remove_attributes, flags);
>         tree type2 = strip_typedefs (TRAIT_TYPE_TYPE2 (t),
>                                      remove_attributes, flags);
>         if (type1 == TRAIT_TYPE_TYPE1 (t) && type2 == TRAIT_TYPE_TYPE2 (t))
>           result = NULL_TREE;
>         else
> -         result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
> +         result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2,
> +                                     tf_warning_or_error);
>        }
>        break;
>      case TYPE_PACK_EXPANSION:
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index 69b1268cfec..f5548737e4c 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -1632,7 +1632,7 @@ structural_comptypes (tree t1, tree t2, int strict)
>      case TRAIT_TYPE:
>        if (TRAIT_TYPE_KIND (t1) != TRAIT_TYPE_KIND (t2))
>         return false;
> -      if (!same_type_p (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
> +      if (!cp_tree_equal (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
>           || !cp_tree_equal (TRAIT_TYPE_TYPE2 (t1), TRAIT_TYPE_TYPE2 (t2)))
>         return false;
>        break;
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element1.C b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> new file mode 100644
> index 00000000000..46858555502
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> @@ -0,0 +1,19 @@
> +// { dg-do compile { target c++11 } }
> +
> +using ty0 = __type_pack_element<0, int>;
> +using ty0 = __type_pack_element<0, int, char>;
> +using ty0 = int;
> +
> +using ty1 = __type_pack_element<1, int, char>;
> +using ty1 = __type_pack_element<(6 - 5) * 1, int, char>;
> +using ty1 = char;
> +
> +template<int N, class... Ts>
> +using __const_type_pack_element_t = const __type_pack_element<N, Ts...>;
> +
> +using ty2 = __const_type_pack_element_t<2, int, char, long>;
> +using ty2 = const long;
> +
> +template<class T> struct A { };
> +using ty3 = __type_pack_element<3, int, int, int, A<int>>;
> +using ty3 = A<int>;
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element2.C b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> new file mode 100644
> index 00000000000..363cfa92514
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> @@ -0,0 +1,14 @@
> +// { dg-do compile { target c++11 } }
> +
> +int p;
> +
> +using type = __type_pack_element<&p, int>; // { dg-error "not an integral constant" }
> +using type = __type_pack_element<1, int>; // { dg-error "out of range" }
> +using type = __type_pack_element<-1, int>; // { dg-error "negative" }
> +using type = __type_pack_element<2, int, char>; // { dg-error "out of range" }
> +
> +template<int N, class... Ts>
> +using __type_pack_element_t = __type_pack_element<N, Ts...>;
> +// { dg-error "out of range" "" { target *-*-* } .-1 }
> +
> +using type = __type_pack_element_t<3, int, char, long>; // { dg-message "here" }
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element3.C b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
> new file mode 100644
> index 00000000000..269f84f464f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
> @@ -0,0 +1,22 @@
> +// { dg-do compile { target c++11 } }
> +
> +template<class T, T N, class... Ts, class = __type_pack_element<N, Ts...>>
> +constexpr int f(int) { return 1; }
> +
> +template<class T, T N, class... Ts>
> +constexpr int f(...) { return 2; };
> +
> +int p;
> +
> +static_assert(f<int, 0, void, char>(0) == 1, "");
> +static_assert(f<int, 1, void, char>(0) == 1, "");
> +static_assert(f<int, 2, void, char>(0) == 2, "");
> +static_assert(f<int*, &p, void, char>(0) == 2, "");
> +
> +template<class T, class U> struct A;
> +template<class T> struct A<T, __type_pack_element<sizeof(T), void, long>> { };
> +template struct A<char, long>;
> +
> +template<class T, class U> struct B;
> +template<class T> struct B<T, __type_pack_element<0, T, short>> { };
> +template struct B<int, int>;
> diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
> index 6a192e27836..5d524d33b4c 100644
> --- a/libstdc++-v3/include/bits/utility.h
> +++ b/libstdc++-v3/include/bits/utility.h
> @@ -224,6 +224,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  #endif // C++17
>  #endif // C++14
>
> +#if __has_builtin(__type_pack_element)
> +  template<size_t _Np, typename... _Types>
> +    struct _Nth_type
> +    { using type = __type_pack_element<_Np, _Types...>; };
> +#else
>    template<size_t _Np, typename... _Types>
>      struct _Nth_type
>      { };
> @@ -262,6 +267,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      struct _Nth_type<1, _Tp0, _Tp1, _Tp2, _Rest...>
>      { using type = _Tp1; };
>  #endif
> +#endif
>
>  _GLIBCXX_END_NAMESPACE_VERSION
>  } // namespace
> --
> 2.39.0.198.ga38d39a4c5
>


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2023-01-09 19:25     ` Patrick Palka
  2023-01-10 10:17       ` Jonathan Wakely
@ 2023-01-17 18:04       ` Jason Merrill
  2023-01-25 20:35         ` Patrick Palka
  1 sibling, 1 reply; 14+ messages in thread
From: Jason Merrill @ 2023-01-17 18:04 UTC (permalink / raw)
  To: Patrick Palka; +Cc: Jonathan Wakely, libstdc++, gcc-patches, Marek Polacek

On 1/9/23 14:25, Patrick Palka via Gcc-patches wrote:
> On Mon, 9 Jan 2023, Patrick Palka wrote:
> 
>> On Wed, 5 Oct 2022, Patrick Palka wrote:
>>
>>> On Thu, 7 Jul 2022, Jonathan Wakely via Gcc-patches wrote:
>>>
>>>> This adds a new built-in to replace the recursive class template
>>>> instantiations done by traits such as std::tuple_element and
>>>> std::variant_alternative. The purpose is to select the Nth type from a
>>>> list of types, e.g. __builtin_type_pack_element(1, char, int, float) is
>>>> int.
>>>>
>>>> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
>>>> the compilation time is reduced by more than 90% and the memory  used by
>>>> the compiler is reduced by 97%. In realistic examples the gains will be
>>>> much smaller, but still relevant.
>>>>
>>>> Clang has a similar built-in, __type_pack_element<N, T...>, but that's a
>>>> "magic template" built-in using <> syntax, which GCC doesn't support. So
>>>> this provides an equivalent feature, but as a built-in function using
>>>> parens instead of <>. I don't really like the name "type pack element"
>>>> (it gives you an element from a pack of types) but the semi-consistency
>>>> with Clang seems like a reasonable argument in favour of keeping the
>>>> name. I'd be open to alternative names though, e.g. __builtin_nth_type
>>>> or __builtin_type_at_index.
>>>
>>> Rather than giving the trait a different name from __type_pack_element,
>>> I wonder if we could just special case cp_parser_trait to expect <>
>>> instead of parens for this trait?
>>>
>>> Btw the frontend recently got a generic TRAIT_TYPE tree code, which gets
>>> rid of much of the boilerplate of adding a new type-yielding built-in
>>> trait, see e.g. cp-trait.def.
>>
>> Here's a tested patch based on Jonathan's original patch that implements
>> the built-in in terms of TRAIT_TYPE, names it __type_pack_element
>> instead of __builtin_type_pack_element, and treats invocations of it
>> like a template-id instead of a call (to match Clang).
>>
>> -- >8 --
>>
>> Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
>>
>> This adds a new built-in to replace the recursive class template
>> instantiations done by traits such as std::tuple_element and
>> std::variant_alternative.  The purpose is to select the Nth type from a
>> list of types, e.g. __type_pack_element<1, char, int, float> is int.
>> We implement it as a special kind of TRAIT_TYPE.
>>
>> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
>> the compilation time is reduced by more than 90% and the memory  used by
>> the compiler is reduced by 97%.  In realistic examples the gains will be
>> much smaller, but still relevant.
>>
>> Unlike the other built-in traits, __type_pack_element uses template-id
>> syntax instead of call syntax and is SFINAE-enabled, matching Clang's
>> implementation.  And like the other built-in traits, it's not mangleable
>> so we can't use it directly in function signatures.
>>
>> Some caveats:
>>
>>    * Clang's version of the built-in seems to act like a "magic template"
>>      that can e.g. be used as a template template argument.  For simplicity
>>      we implement it in a more ad-hoc way.
>>    * Our parsing of the <>'s in __type_pack_element<...> is currently
>>      rudimentary and doesn't try to disambiguate a trailing >> vs > >
>>      as cp_parser_enclosed_template_argument_list does.
> 
> Hmm, this latter caveat turns out to be inconvenient (for code such as
> type_pack_element3.C) and admits an easy workaround inspired by what
> cp_parser_enclosed_template_argument_list does.
> 
> v2: Consider the >> in __type_pack_element<0, int, char>> to be two >'s.
>      Handle non-type TRAIT_TYPE_TYPE1 in strip_typedefs (for sake of
>      CPTK_TYPE_PACK_ELEMENT).

Why not use cp_parser_enclosed_template_argument_list directly?

> -- >8 --
> 
> Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
> 
> This adds a new built-in to replace the recursive class template
> instantiations done by traits such as std::tuple_element and
> std::variant_alternative.  The purpose is to select the Nth type from a
> list of types, e.g. __type_pack_element<1, char, int, float> is int.
> We implement it as a special kind of TRAIT_TYPE.
> 
> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> the compilation time is reduced by more than 90% and the memory  used by
> the compiler is reduced by 97%.  In realistic examples the gains will be
> much smaller, but still relevant.
> 
> Unlike the other built-in traits, __type_pack_element uses template-id
> syntax instead of call syntax and is SFINAE-enabled, matching Clang's
> implementation.  And like the other built-in traits, it's not mangleable
> so we can't use it directly in function signatures.
> 
> N.B. Clang seems to implement __type_pack_element as a first-class
> template that can e.g. be used as a template template argument.  For
> simplicity we implement it in a more ad-hoc way.
> 
> Co-authored-by: Jonathan Wakely <jwakely@redhat.com>
> 
> 	PR c++/100157
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-trait.def (TYPE_PACK_ELEMENT): Define.
> 	* cp-tree.h (finish_trait_type): Add complain parameter.
> 	* cxx-pretty-print.cc (pp_cxx_trait): Handle
> 	CPTK_TYPE_PACK_ELEMENT.
> 	* parser.cc (cp_parser_constant_expression): Document default
> 	arguments.
> 	(cp_parser_trait): Handle CPTK_TYPE_PACK_ELEMENT.  Pass
> 	tf_warning_or_error to finish_trait_type.
> 	* pt.cc (tsubst) <case TRAIT_TYPE>: Handle
> 	CPTK_TYPE_PACK_ELEMENT.
> 	* semantics.cc (finish_type_pack_element): Define.
> 	(finish_trait_type): Add complain parameter.  Handle
> 	CPTK_TYPE_PACK_ELEMENT.
> 	* tree.cc (strip_typedefs): Handle non-type TRAIT_TYPE_TYPE1.
> 	Pass tf_warning_or_error to finish_trait_type.
> 	* typeck.cc (structural_comptypes) <case TRAIT_TYPE>: Use
> 	cp_tree_equal instead of same_type_p for TRAIT_TYPE_TYPE1.
> 
> libstdc++-v3/ChangeLog:
> 
> 	* include/bits/utility.h (_Nth_type): Conditionally define in
> 	terms of __type_pack_element if available.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/ext/type_pack_element1.C: New test.
> 	* g++.dg/ext/type_pack_element2.C: New test.
> 	* g++.dg/ext/type_pack_element3.C: New test.
> ---
>   gcc/cp/cp-trait.def                           |  1 +
>   gcc/cp/cp-tree.h                              |  2 +-
>   gcc/cp/cxx-pretty-print.cc                    | 17 +++++--
>   gcc/cp/parser.cc                              | 46 +++++++++++++++----
>   gcc/cp/pt.cc                                  |  8 +++-
>   gcc/cp/semantics.cc                           | 39 +++++++++++++++-
>   gcc/cp/tree.cc                                | 10 ++--
>   gcc/cp/typeck.cc                              |  2 +-
>   gcc/testsuite/g++.dg/ext/type_pack_element1.C | 19 ++++++++
>   gcc/testsuite/g++.dg/ext/type_pack_element2.C | 14 ++++++
>   gcc/testsuite/g++.dg/ext/type_pack_element3.C | 22 +++++++++
>   libstdc++-v3/include/bits/utility.h           |  6 +++
>   12 files changed, 165 insertions(+), 21 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element1.C
>   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element2.C
>   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element3.C
> 
> diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
> index 823899a26c5..63f6b101eeb 100644
> --- a/gcc/cp/cp-trait.def
> +++ b/gcc/cp/cp-trait.def
> @@ -89,6 +89,7 @@ DEFTRAIT_TYPE (REMOVE_CV, "__remove_cv", 1)
>   DEFTRAIT_TYPE (REMOVE_REFERENCE, "__remove_reference", 1)
>   DEFTRAIT_TYPE (REMOVE_CVREF, "__remove_cvref", 1)
>   DEFTRAIT_TYPE (UNDERLYING_TYPE,  "__underlying_type", 1)
> +DEFTRAIT_TYPE (TYPE_PACK_ELEMENT, "__type_pack_element", -1)
>   
>   /* These traits yield a type pack, not a type, and are represented by
>      cp_parser_trait as a special BASES tree instead of a TRAIT_TYPE tree.  */
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 1f4967c2ba0..f3cc2c5874c 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7764,7 +7764,7 @@ extern tree finish_decltype_type                (tree, bool, tsubst_flags_t);
>   extern tree fold_builtin_is_corresponding_member (location_t, int, tree *);
>   extern tree fold_builtin_is_pointer_inverconvertible_with_class (location_t, int, tree *);
>   extern tree finish_trait_expr			(location_t, enum cp_trait_kind, tree, tree);
> -extern tree finish_trait_type			(enum cp_trait_kind, tree, tree);
> +extern tree finish_trait_type			(enum cp_trait_kind, tree, tree, tsubst_flags_t);
>   extern tree build_lambda_expr                   (void);
>   extern tree build_lambda_object			(tree);
>   extern tree begin_lambda_type                   (tree);
> diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
> index 8ca1b8f234a..07a0406e491 100644
> --- a/gcc/cp/cxx-pretty-print.cc
> +++ b/gcc/cp/cxx-pretty-print.cc
> @@ -2625,8 +2625,16 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
>   #undef DEFTRAIT
>       }
>   
> -  pp_cxx_left_paren (pp);
> -  pp->type_id (type1);
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    {
> +      pp_cxx_begin_template_argument_list (pp);
> +      pp->expression (type1);
> +    }
> +  else
> +    {
> +      pp_cxx_left_paren (pp);
> +      pp->type_id (type1);
> +    }
>     if (type2)
>       {
>         if (TREE_CODE (type2) != TREE_LIST)
> @@ -2641,7 +2649,10 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
>   	    pp->type_id (TREE_VALUE (arg));
>   	  }
>       }
> -  pp_cxx_right_paren (pp);
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    pp_cxx_end_template_argument_list (pp);
> +  else
> +    pp_cxx_right_paren (pp);
>   }
>   
>   // requires-clause:
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index 8b1658decba..0a88d83d955 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -10683,9 +10683,9 @@ cp_parser_expression (cp_parser* parser, cp_id_kind * pidk,
>   
>   static cp_expr
>   cp_parser_constant_expression (cp_parser* parser,
> -			       int allow_non_constant_p,
> -			       bool *non_constant_p,
> -			       bool strict_p)
> +			       int allow_non_constant_p /* = 0 */,
> +			       bool *non_constant_p /* = NULL */,
> +			       bool strict_p /* = false */)
>   {
>     bool saved_integral_constant_expression_p;
>     bool saved_allow_non_integral_constant_expression_p;
> @@ -10912,12 +10912,25 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
>     cp_lexer_consume_token (parser->lexer);
>   
>     matching_parens parens;
> -  parens.require_open (parser);
>   
> -  {
> -    type_id_in_expr_sentinel s (parser);
> -    type1 = cp_parser_type_id (parser);
> -  }
> +  /* Unlike the other built-in traits, __type_pack_element is unique in that
> +     it takes an expression as its first argument and it uses template-id
> +     syntax instead of function call syntax (for compatibility with Clang).
> +     We special case these properties of __type_pack_element here and elsewhere.
> +
> +     TODO: Generalize the trait infrastructure so that __type_pack_element
> +     is no longer a special case.  */
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    {
> +      cp_parser_require (parser, CPP_LESS, RT_LESS);
> +      type1 = cp_parser_constant_expression (parser);
> +    }
> +  else
> +    {
> +      parens.require_open (parser);
> +      type_id_in_expr_sentinel s (parser);
> +      type1 = cp_parser_type_id (parser);
> +    }
>   
>     if (type1 == error_mark_node)
>       return error_mark_node;
> @@ -10953,7 +10966,20 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
>       }
>   
>     location_t finish_loc = cp_lexer_peek_token (parser->lexer)->location;
> -  parens.require_close (parser);
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    {
> +      /* As in cp_parser_enclosed_template_argument_list, a trailing
> +	 '>>' here is considered to be two separate '>' tokens.  */
> +      if (cp_lexer_next_token_is (parser->lexer, CPP_RSHIFT))
> +	{
> +	  cp_token *token = cp_lexer_peek_token (parser->lexer);
> +	  token->type = CPP_GREATER;
> +	}
> +      else
> +	cp_parser_require (parser, CPP_GREATER, RT_GREATER);
> +    }
> +  else
> +    parens.require_close (parser);
>   
>     /* Construct a location of the form:
>          __is_trivially_copyable(_Tp)
> @@ -10971,7 +10997,7 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
>         return cp_expr (finish_bases (type1, true), trait_loc);
>       default:
>         if (type)
> -	return finish_trait_type (kind, type1, type2);
> +	return finish_trait_type (kind, type1, type2, tf_warning_or_error);
>         else
>   	return finish_trait_expr (trait_loc, kind, type1, type2);
>       }
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index cbe5898b553..2e83970a252 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -16576,9 +16576,13 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>   
>       case TRAIT_TYPE:
>         {
> -	tree type1 = tsubst (TRAIT_TYPE_TYPE1 (t), args, complain, in_decl);
> +	tree type1 = TRAIT_TYPE_TYPE1 (t);
> +	if (TRAIT_TYPE_KIND (t) != CPTK_TYPE_PACK_ELEMENT)
> +	  type1 = tsubst (type1, args, complain, in_decl);
> +	else
> +	  type1 = tsubst_copy_and_build (type1, args, complain, in_decl);
>   	tree type2 = tsubst (TRAIT_TYPE_TYPE2 (t), args, complain, in_decl);
> -	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
> +	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2, complain);
>   	return cp_build_qualified_type (type,
>   					cp_type_quals (t) | cp_type_quals (type),
>   					complain | tf_ignore_bad_quals);
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index ef5bf2430b1..af9edf59b3b 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -4457,6 +4457,36 @@ finish_underlying_type (tree type)
>     return underlying_type;
>   }
>   
> +/* 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)
> +{
> +  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");
> +      return error_mark_node;
> +    }
> +  HOST_WIDE_INT val = tree_to_shwi (idx);
> +  if (val < 0)
> +    {
> +      if (complain & tf_error)
> +	error ("%<__type_pack_element%> index is negative");
> +      return error_mark_node;
> +    }
> +  tree result = chain_index (val, types);
> +  if (!result)
> +    {
> +      if (complain & tf_error)
> +	error ("%<__type_pack_element%> index is out of range");
> +      return error_mark_node;
> +    }
> +  return TREE_VALUE (result);
> +}
> +
>   /* Implement the __direct_bases keyword: Return the direct base classes
>      of type.  */
>   
> @@ -12213,7 +12243,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
>   /* Process a trait type.  */
>   
>   tree
> -finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
> +finish_trait_type (cp_trait_kind kind, tree type1, tree type2,
> +		   tsubst_flags_t complain)
>   {
>     if (type1 == error_mark_node
>         || type2 == error_mark_node)
> @@ -12237,17 +12268,23 @@ finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
>       {
>       case CPTK_UNDERLYING_TYPE:
>         return finish_underlying_type (type1);
> +
>       case CPTK_REMOVE_CV:
>         return cv_unqualified (type1);
> +
>       case CPTK_REMOVE_REFERENCE:
>         if (TYPE_REF_P (type1))
>   	type1 = TREE_TYPE (type1);
>         return type1;
> +
>       case CPTK_REMOVE_CVREF:
>         if (TYPE_REF_P (type1))
>   	type1 = TREE_TYPE (type1);
>         return cv_unqualified (type1);
>   
> +    case CPTK_TYPE_PACK_ELEMENT:
> +      return finish_type_pack_element (type1, type2, complain);
> +
>   #define DEFTRAIT_EXPR(CODE, NAME, ARITY) \
>       case CPTK_##CODE:
>   #include "cp-trait.def"
> diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> index faf01616f87..d834a0fd38b 100644
> --- a/gcc/cp/tree.cc
> +++ b/gcc/cp/tree.cc
> @@ -1792,14 +1792,18 @@ strip_typedefs (tree t, bool *remove_attributes /* = NULL */,
>         break;
>       case TRAIT_TYPE:
>         {
> -	tree type1 = strip_typedefs (TRAIT_TYPE_TYPE1 (t),
> -				     remove_attributes, flags);
> +	tree type1 = TRAIT_TYPE_TYPE1 (t);
> +	if (TYPE_P (type1))
> +	  type1 = strip_typedefs (type1, remove_attributes, flags);
> +	else
> +	  type1 = strip_typedefs_expr (type1, remove_attributes, flags);
>   	tree type2 = strip_typedefs (TRAIT_TYPE_TYPE2 (t),
>   				     remove_attributes, flags);
>   	if (type1 == TRAIT_TYPE_TYPE1 (t) && type2 == TRAIT_TYPE_TYPE2 (t))
>   	  result = NULL_TREE;
>   	else
> -	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
> +	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2,
> +				      tf_warning_or_error);
>         }
>         break;
>       case TYPE_PACK_EXPANSION:
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index 69b1268cfec..f5548737e4c 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -1632,7 +1632,7 @@ structural_comptypes (tree t1, tree t2, int strict)
>       case TRAIT_TYPE:
>         if (TRAIT_TYPE_KIND (t1) != TRAIT_TYPE_KIND (t2))
>   	return false;
> -      if (!same_type_p (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
> +      if (!cp_tree_equal (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
>   	  || !cp_tree_equal (TRAIT_TYPE_TYPE2 (t1), TRAIT_TYPE_TYPE2 (t2)))
>   	return false;
>         break;
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element1.C b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> new file mode 100644
> index 00000000000..46858555502
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> @@ -0,0 +1,19 @@
> +// { dg-do compile { target c++11 } }
> +
> +using ty0 = __type_pack_element<0, int>;
> +using ty0 = __type_pack_element<0, int, char>;
> +using ty0 = int;
> +
> +using ty1 = __type_pack_element<1, int, char>;
> +using ty1 = __type_pack_element<(6 - 5) * 1, int, char>;
> +using ty1 = char;
> +
> +template<int N, class... Ts>
> +using __const_type_pack_element_t = const __type_pack_element<N, Ts...>;
> +
> +using ty2 = __const_type_pack_element_t<2, int, char, long>;
> +using ty2 = const long;
> +
> +template<class T> struct A { };
> +using ty3 = __type_pack_element<3, int, int, int, A<int>>;
> +using ty3 = A<int>;
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element2.C b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> new file mode 100644
> index 00000000000..363cfa92514
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> @@ -0,0 +1,14 @@
> +// { dg-do compile { target c++11 } }
> +
> +int p;
> +
> +using type = __type_pack_element<&p, int>; // { dg-error "not an integral constant" }
> +using type = __type_pack_element<1, int>; // { dg-error "out of range" }
> +using type = __type_pack_element<-1, int>; // { dg-error "negative" }
> +using type = __type_pack_element<2, int, char>; // { dg-error "out of range" }
> +
> +template<int N, class... Ts>
> +using __type_pack_element_t = __type_pack_element<N, Ts...>;
> +// { dg-error "out of range" "" { target *-*-* } .-1 }
> +
> +using type = __type_pack_element_t<3, int, char, long>; // { dg-message "here" }
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element3.C b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
> new file mode 100644
> index 00000000000..269f84f464f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
> @@ -0,0 +1,22 @@
> +// { dg-do compile { target c++11 } }
> +
> +template<class T, T N, class... Ts, class = __type_pack_element<N, Ts...>>
> +constexpr int f(int) { return 1; }
> +
> +template<class T, T N, class... Ts>
> +constexpr int f(...) { return 2; };
> +
> +int p;
> +
> +static_assert(f<int, 0, void, char>(0) == 1, "");
> +static_assert(f<int, 1, void, char>(0) == 1, "");
> +static_assert(f<int, 2, void, char>(0) == 2, "");
> +static_assert(f<int*, &p, void, char>(0) == 2, "");
> +
> +template<class T, class U> struct A;
> +template<class T> struct A<T, __type_pack_element<sizeof(T), void, long>> { };
> +template struct A<char, long>;
> +
> +template<class T, class U> struct B;
> +template<class T> struct B<T, __type_pack_element<0, T, short>> { };
> +template struct B<int, int>;
> diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
> index 6a192e27836..5d524d33b4c 100644
> --- a/libstdc++-v3/include/bits/utility.h
> +++ b/libstdc++-v3/include/bits/utility.h
> @@ -224,6 +224,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>   #endif // C++17
>   #endif // C++14
>   
> +#if __has_builtin(__type_pack_element)
> +  template<size_t _Np, typename... _Types>
> +    struct _Nth_type
> +    { using type = __type_pack_element<_Np, _Types...>; };
> +#else
>     template<size_t _Np, typename... _Types>
>       struct _Nth_type
>       { };
> @@ -262,6 +267,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       struct _Nth_type<1, _Tp0, _Tp1, _Tp2, _Rest...>
>       { using type = _Tp1; };
>   #endif
> +#endif
>   
>   _GLIBCXX_END_NAMESPACE_VERSION
>   } // namespace


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2023-01-17 18:04       ` Jason Merrill
@ 2023-01-25 20:35         ` Patrick Palka
  2023-01-26 17:47           ` Jason Merrill
  0 siblings, 1 reply; 14+ messages in thread
From: Patrick Palka @ 2023-01-25 20:35 UTC (permalink / raw)
  To: Jason Merrill
  Cc: Patrick Palka, Jonathan Wakely, libstdc++, gcc-patches, Marek Polacek

On Tue, 17 Jan 2023, Jason Merrill wrote:

> On 1/9/23 14:25, Patrick Palka via Gcc-patches wrote:
> > On Mon, 9 Jan 2023, Patrick Palka wrote:
> > 
> > > On Wed, 5 Oct 2022, Patrick Palka wrote:
> > > 
> > > > On Thu, 7 Jul 2022, Jonathan Wakely via Gcc-patches wrote:
> > > > 
> > > > > This adds a new built-in to replace the recursive class template
> > > > > instantiations done by traits such as std::tuple_element and
> > > > > std::variant_alternative. The purpose is to select the Nth type from a
> > > > > list of types, e.g. __builtin_type_pack_element(1, char, int, float)
> > > > > is
> > > > > int.
> > > > > 
> > > > > For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> > > > > the compilation time is reduced by more than 90% and the memory  used
> > > > > by
> > > > > the compiler is reduced by 97%. In realistic examples the gains will
> > > > > be
> > > > > much smaller, but still relevant.
> > > > > 
> > > > > Clang has a similar built-in, __type_pack_element<N, T...>, but that's
> > > > > a
> > > > > "magic template" built-in using <> syntax, which GCC doesn't support.
> > > > > So
> > > > > this provides an equivalent feature, but as a built-in function using
> > > > > parens instead of <>. I don't really like the name "type pack element"
> > > > > (it gives you an element from a pack of types) but the
> > > > > semi-consistency
> > > > > with Clang seems like a reasonable argument in favour of keeping the
> > > > > name. I'd be open to alternative names though, e.g. __builtin_nth_type
> > > > > or __builtin_type_at_index.
> > > > 
> > > > Rather than giving the trait a different name from __type_pack_element,
> > > > I wonder if we could just special case cp_parser_trait to expect <>
> > > > instead of parens for this trait?
> > > > 
> > > > Btw the frontend recently got a generic TRAIT_TYPE tree code, which gets
> > > > rid of much of the boilerplate of adding a new type-yielding built-in
> > > > trait, see e.g. cp-trait.def.
> > > 
> > > Here's a tested patch based on Jonathan's original patch that implements
> > > the built-in in terms of TRAIT_TYPE, names it __type_pack_element
> > > instead of __builtin_type_pack_element, and treats invocations of it
> > > like a template-id instead of a call (to match Clang).
> > > 
> > > -- >8 --
> > > 
> > > Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
> > > 
> > > This adds a new built-in to replace the recursive class template
> > > instantiations done by traits such as std::tuple_element and
> > > std::variant_alternative.  The purpose is to select the Nth type from a
> > > list of types, e.g. __type_pack_element<1, char, int, float> is int.
> > > We implement it as a special kind of TRAIT_TYPE.
> > > 
> > > For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> > > the compilation time is reduced by more than 90% and the memory  used by
> > > the compiler is reduced by 97%.  In realistic examples the gains will be
> > > much smaller, but still relevant.
> > > 
> > > Unlike the other built-in traits, __type_pack_element uses template-id
> > > syntax instead of call syntax and is SFINAE-enabled, matching Clang's
> > > implementation.  And like the other built-in traits, it's not mangleable
> > > so we can't use it directly in function signatures.
> > > 
> > > Some caveats:
> > > 
> > >    * Clang's version of the built-in seems to act like a "magic template"
> > >      that can e.g. be used as a template template argument.  For
> > > simplicity
> > >      we implement it in a more ad-hoc way.
> > >    * Our parsing of the <>'s in __type_pack_element<...> is currently
> > >      rudimentary and doesn't try to disambiguate a trailing >> vs > >
> > >      as cp_parser_enclosed_template_argument_list does.
> > 
> > Hmm, this latter caveat turns out to be inconvenient (for code such as
> > type_pack_element3.C) and admits an easy workaround inspired by what
> > cp_parser_enclosed_template_argument_list does.
> > 
> > v2: Consider the >> in __type_pack_element<0, int, char>> to be two >'s.
> >      Handle non-type TRAIT_TYPE_TYPE1 in strip_typedefs (for sake of
> >      CPTK_TYPE_PACK_ELEMENT).
> 
> Why not use cp_parser_enclosed_template_argument_list directly?

If we used cp_parser_enclosed_template_argument_list we would then need
to convert the returned TREE_VEC into a TREE_LIST and also diagnose
argument kind mismatches (i.e. verify the first argument is an
expression and the rest are types).  It seemed like more complexity
overall then just duplicating the >> splitting logic, but I can do that
if you prefer?

> 
> > -- >8 --
> > 
> > Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
> > 
> > This adds a new built-in to replace the recursive class template
> > instantiations done by traits such as std::tuple_element and
> > std::variant_alternative.  The purpose is to select the Nth type from a
> > list of types, e.g. __type_pack_element<1, char, int, float> is int.
> > We implement it as a special kind of TRAIT_TYPE.
> > 
> > For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> > the compilation time is reduced by more than 90% and the memory  used by
> > the compiler is reduced by 97%.  In realistic examples the gains will be
> > much smaller, but still relevant.
> > 
> > Unlike the other built-in traits, __type_pack_element uses template-id
> > syntax instead of call syntax and is SFINAE-enabled, matching Clang's
> > implementation.  And like the other built-in traits, it's not mangleable
> > so we can't use it directly in function signatures.
> > 
> > N.B. Clang seems to implement __type_pack_element as a first-class
> > template that can e.g. be used as a template template argument.  For
> > simplicity we implement it in a more ad-hoc way.
> > 
> > Co-authored-by: Jonathan Wakely <jwakely@redhat.com>
> > 
> > 	PR c++/100157
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* cp-trait.def (TYPE_PACK_ELEMENT): Define.
> > 	* cp-tree.h (finish_trait_type): Add complain parameter.
> > 	* cxx-pretty-print.cc (pp_cxx_trait): Handle
> > 	CPTK_TYPE_PACK_ELEMENT.
> > 	* parser.cc (cp_parser_constant_expression): Document default
> > 	arguments.
> > 	(cp_parser_trait): Handle CPTK_TYPE_PACK_ELEMENT.  Pass
> > 	tf_warning_or_error to finish_trait_type.
> > 	* pt.cc (tsubst) <case TRAIT_TYPE>: Handle
> > 	CPTK_TYPE_PACK_ELEMENT.
> > 	* semantics.cc (finish_type_pack_element): Define.
> > 	(finish_trait_type): Add complain parameter.  Handle
> > 	CPTK_TYPE_PACK_ELEMENT.
> > 	* tree.cc (strip_typedefs): Handle non-type TRAIT_TYPE_TYPE1.
> > 	Pass tf_warning_or_error to finish_trait_type.
> > 	* typeck.cc (structural_comptypes) <case TRAIT_TYPE>: Use
> > 	cp_tree_equal instead of same_type_p for TRAIT_TYPE_TYPE1.
> > 
> > libstdc++-v3/ChangeLog:
> > 
> > 	* include/bits/utility.h (_Nth_type): Conditionally define in
> > 	terms of __type_pack_element if available.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/ext/type_pack_element1.C: New test.
> > 	* g++.dg/ext/type_pack_element2.C: New test.
> > 	* g++.dg/ext/type_pack_element3.C: New test.
> > ---
> >   gcc/cp/cp-trait.def                           |  1 +
> >   gcc/cp/cp-tree.h                              |  2 +-
> >   gcc/cp/cxx-pretty-print.cc                    | 17 +++++--
> >   gcc/cp/parser.cc                              | 46 +++++++++++++++----
> >   gcc/cp/pt.cc                                  |  8 +++-
> >   gcc/cp/semantics.cc                           | 39 +++++++++++++++-
> >   gcc/cp/tree.cc                                | 10 ++--
> >   gcc/cp/typeck.cc                              |  2 +-
> >   gcc/testsuite/g++.dg/ext/type_pack_element1.C | 19 ++++++++
> >   gcc/testsuite/g++.dg/ext/type_pack_element2.C | 14 ++++++
> >   gcc/testsuite/g++.dg/ext/type_pack_element3.C | 22 +++++++++
> >   libstdc++-v3/include/bits/utility.h           |  6 +++
> >   12 files changed, 165 insertions(+), 21 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element1.C
> >   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element2.C
> >   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element3.C
> > 
> > diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
> > index 823899a26c5..63f6b101eeb 100644
> > --- a/gcc/cp/cp-trait.def
> > +++ b/gcc/cp/cp-trait.def
> > @@ -89,6 +89,7 @@ DEFTRAIT_TYPE (REMOVE_CV, "__remove_cv", 1)
> >   DEFTRAIT_TYPE (REMOVE_REFERENCE, "__remove_reference", 1)
> >   DEFTRAIT_TYPE (REMOVE_CVREF, "__remove_cvref", 1)
> >   DEFTRAIT_TYPE (UNDERLYING_TYPE,  "__underlying_type", 1)
> > +DEFTRAIT_TYPE (TYPE_PACK_ELEMENT, "__type_pack_element", -1)
> >     /* These traits yield a type pack, not a type, and are represented by
> >      cp_parser_trait as a special BASES tree instead of a TRAIT_TYPE tree.
> > */
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index 1f4967c2ba0..f3cc2c5874c 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -7764,7 +7764,7 @@ extern tree finish_decltype_type                (tree,
> > bool, tsubst_flags_t);
> >   extern tree fold_builtin_is_corresponding_member (location_t, int, tree
> > *);
> >   extern tree fold_builtin_is_pointer_inverconvertible_with_class
> > (location_t, int, tree *);
> >   extern tree finish_trait_expr			(location_t, enum
> > cp_trait_kind, tree, tree);
> > -extern tree finish_trait_type			(enum cp_trait_kind,
> > tree, tree);
> > +extern tree finish_trait_type			(enum cp_trait_kind,
> > tree, tree, tsubst_flags_t);
> >   extern tree build_lambda_expr                   (void);
> >   extern tree build_lambda_object			(tree);
> >   extern tree begin_lambda_type                   (tree);
> > diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
> > index 8ca1b8f234a..07a0406e491 100644
> > --- a/gcc/cp/cxx-pretty-print.cc
> > +++ b/gcc/cp/cxx-pretty-print.cc
> > @@ -2625,8 +2625,16 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
> >   #undef DEFTRAIT
> >       }
> >   -  pp_cxx_left_paren (pp);
> > -  pp->type_id (type1);
> > +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> > +    {
> > +      pp_cxx_begin_template_argument_list (pp);
> > +      pp->expression (type1);
> > +    }
> > +  else
> > +    {
> > +      pp_cxx_left_paren (pp);
> > +      pp->type_id (type1);
> > +    }
> >     if (type2)
> >       {
> >         if (TREE_CODE (type2) != TREE_LIST)
> > @@ -2641,7 +2649,10 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
> >   	    pp->type_id (TREE_VALUE (arg));
> >   	  }
> >       }
> > -  pp_cxx_right_paren (pp);
> > +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> > +    pp_cxx_end_template_argument_list (pp);
> > +  else
> > +    pp_cxx_right_paren (pp);
> >   }
> >     // requires-clause:
> > diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> > index 8b1658decba..0a88d83d955 100644
> > --- a/gcc/cp/parser.cc
> > +++ b/gcc/cp/parser.cc
> > @@ -10683,9 +10683,9 @@ cp_parser_expression (cp_parser* parser, cp_id_kind
> > * pidk,
> >     static cp_expr
> >   cp_parser_constant_expression (cp_parser* parser,
> > -			       int allow_non_constant_p,
> > -			       bool *non_constant_p,
> > -			       bool strict_p)
> > +			       int allow_non_constant_p /* = 0 */,
> > +			       bool *non_constant_p /* = NULL */,
> > +			       bool strict_p /* = false */)
> >   {
> >     bool saved_integral_constant_expression_p;
> >     bool saved_allow_non_integral_constant_expression_p;
> > @@ -10912,12 +10912,25 @@ cp_parser_trait (cp_parser* parser, enum rid
> > keyword)
> >     cp_lexer_consume_token (parser->lexer);
> >       matching_parens parens;
> > -  parens.require_open (parser);
> >   -  {
> > -    type_id_in_expr_sentinel s (parser);
> > -    type1 = cp_parser_type_id (parser);
> > -  }
> > +  /* Unlike the other built-in traits, __type_pack_element is unique in
> > that
> > +     it takes an expression as its first argument and it uses template-id
> > +     syntax instead of function call syntax (for compatibility with Clang).
> > +     We special case these properties of __type_pack_element here and
> > elsewhere.
> > +
> > +     TODO: Generalize the trait infrastructure so that __type_pack_element
> > +     is no longer a special case.  */
> > +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> > +    {
> > +      cp_parser_require (parser, CPP_LESS, RT_LESS);
> > +      type1 = cp_parser_constant_expression (parser);
> > +    }
> > +  else
> > +    {
> > +      parens.require_open (parser);
> > +      type_id_in_expr_sentinel s (parser);
> > +      type1 = cp_parser_type_id (parser);
> > +    }
> >       if (type1 == error_mark_node)
> >       return error_mark_node;
> > @@ -10953,7 +10966,20 @@ cp_parser_trait (cp_parser* parser, enum rid
> > keyword)
> >       }
> >       location_t finish_loc = cp_lexer_peek_token (parser->lexer)->location;
> > -  parens.require_close (parser);
> > +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> > +    {
> > +      /* As in cp_parser_enclosed_template_argument_list, a trailing
> > +	 '>>' here is considered to be two separate '>' tokens.  */
> > +      if (cp_lexer_next_token_is (parser->lexer, CPP_RSHIFT))
> > +	{
> > +	  cp_token *token = cp_lexer_peek_token (parser->lexer);
> > +	  token->type = CPP_GREATER;
> > +	}
> > +      else
> > +	cp_parser_require (parser, CPP_GREATER, RT_GREATER);
> > +    }
> > +  else
> > +    parens.require_close (parser);
> >       /* Construct a location of the form:
> >          __is_trivially_copyable(_Tp)
> > @@ -10971,7 +10997,7 @@ cp_parser_trait (cp_parser* parser, enum rid
> > keyword)
> >         return cp_expr (finish_bases (type1, true), trait_loc);
> >       default:
> >         if (type)
> > -	return finish_trait_type (kind, type1, type2);
> > +	return finish_trait_type (kind, type1, type2, tf_warning_or_error);
> >         else
> >   	return finish_trait_expr (trait_loc, kind, type1, type2);
> >       }
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index cbe5898b553..2e83970a252 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -16576,9 +16576,13 @@ tsubst (tree t, tree args, tsubst_flags_t complain,
> > tree in_decl)
> >         case TRAIT_TYPE:
> >         {
> > -	tree type1 = tsubst (TRAIT_TYPE_TYPE1 (t), args, complain, in_decl);
> > +	tree type1 = TRAIT_TYPE_TYPE1 (t);
> > +	if (TRAIT_TYPE_KIND (t) != CPTK_TYPE_PACK_ELEMENT)
> > +	  type1 = tsubst (type1, args, complain, in_decl);
> > +	else
> > +	  type1 = tsubst_copy_and_build (type1, args, complain, in_decl);
> >   	tree type2 = tsubst (TRAIT_TYPE_TYPE2 (t), args, complain, in_decl);
> > -	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
> > +	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2,
> > complain);
> >   	return cp_build_qualified_type (type,
> >   					cp_type_quals (t) | cp_type_quals
> > (type),
> >   					complain | tf_ignore_bad_quals);
> > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> > index ef5bf2430b1..af9edf59b3b 100644
> > --- a/gcc/cp/semantics.cc
> > +++ b/gcc/cp/semantics.cc
> > @@ -4457,6 +4457,36 @@ finish_underlying_type (tree type)
> >     return underlying_type;
> >   }
> >   +/* 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)
> > +{
> > +  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");
> > +      return error_mark_node;
> > +    }
> > +  HOST_WIDE_INT val = tree_to_shwi (idx);
> > +  if (val < 0)
> > +    {
> > +      if (complain & tf_error)
> > +	error ("%<__type_pack_element%> index is negative");
> > +      return error_mark_node;
> > +    }
> > +  tree result = chain_index (val, types);
> > +  if (!result)
> > +    {
> > +      if (complain & tf_error)
> > +	error ("%<__type_pack_element%> index is out of range");
> > +      return error_mark_node;
> > +    }
> > +  return TREE_VALUE (result);
> > +}
> > +
> >   /* Implement the __direct_bases keyword: Return the direct base classes
> >      of type.  */
> >   @@ -12213,7 +12243,8 @@ finish_trait_expr (location_t loc, cp_trait_kind
> > kind, tree type1, tree type2)
> >   /* Process a trait type.  */
> >     tree
> > -finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
> > +finish_trait_type (cp_trait_kind kind, tree type1, tree type2,
> > +		   tsubst_flags_t complain)
> >   {
> >     if (type1 == error_mark_node
> >         || type2 == error_mark_node)
> > @@ -12237,17 +12268,23 @@ finish_trait_type (cp_trait_kind kind, tree type1,
> > tree type2)
> >       {
> >       case CPTK_UNDERLYING_TYPE:
> >         return finish_underlying_type (type1);
> > +
> >       case CPTK_REMOVE_CV:
> >         return cv_unqualified (type1);
> > +
> >       case CPTK_REMOVE_REFERENCE:
> >         if (TYPE_REF_P (type1))
> >   	type1 = TREE_TYPE (type1);
> >         return type1;
> > +
> >       case CPTK_REMOVE_CVREF:
> >         if (TYPE_REF_P (type1))
> >   	type1 = TREE_TYPE (type1);
> >         return cv_unqualified (type1);
> >   +    case CPTK_TYPE_PACK_ELEMENT:
> > +      return finish_type_pack_element (type1, type2, complain);
> > +
> >   #define DEFTRAIT_EXPR(CODE, NAME, ARITY) \
> >       case CPTK_##CODE:
> >   #include "cp-trait.def"
> > diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> > index faf01616f87..d834a0fd38b 100644
> > --- a/gcc/cp/tree.cc
> > +++ b/gcc/cp/tree.cc
> > @@ -1792,14 +1792,18 @@ strip_typedefs (tree t, bool *remove_attributes /* =
> > NULL */,
> >         break;
> >       case TRAIT_TYPE:
> >         {
> > -	tree type1 = strip_typedefs (TRAIT_TYPE_TYPE1 (t),
> > -				     remove_attributes, flags);
> > +	tree type1 = TRAIT_TYPE_TYPE1 (t);
> > +	if (TYPE_P (type1))
> > +	  type1 = strip_typedefs (type1, remove_attributes, flags);
> > +	else
> > +	  type1 = strip_typedefs_expr (type1, remove_attributes, flags);
> >   	tree type2 = strip_typedefs (TRAIT_TYPE_TYPE2 (t),
> >   				     remove_attributes, flags);
> >   	if (type1 == TRAIT_TYPE_TYPE1 (t) && type2 == TRAIT_TYPE_TYPE2 (t))
> >   	  result = NULL_TREE;
> >   	else
> > -	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
> > +	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2,
> > +				      tf_warning_or_error);
> >         }
> >         break;
> >       case TYPE_PACK_EXPANSION:
> > diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> > index 69b1268cfec..f5548737e4c 100644
> > --- a/gcc/cp/typeck.cc
> > +++ b/gcc/cp/typeck.cc
> > @@ -1632,7 +1632,7 @@ structural_comptypes (tree t1, tree t2, int strict)
> >       case TRAIT_TYPE:
> >         if (TRAIT_TYPE_KIND (t1) != TRAIT_TYPE_KIND (t2))
> >   	return false;
> > -      if (!same_type_p (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
> > +      if (!cp_tree_equal (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
> >   	  || !cp_tree_equal (TRAIT_TYPE_TYPE2 (t1), TRAIT_TYPE_TYPE2 (t2)))
> >   	return false;
> >         break;
> > diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> > b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> > new file mode 100644
> > index 00000000000..46858555502
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> > @@ -0,0 +1,19 @@
> > +// { dg-do compile { target c++11 } }
> > +
> > +using ty0 = __type_pack_element<0, int>;
> > +using ty0 = __type_pack_element<0, int, char>;
> > +using ty0 = int;
> > +
> > +using ty1 = __type_pack_element<1, int, char>;
> > +using ty1 = __type_pack_element<(6 - 5) * 1, int, char>;
> > +using ty1 = char;
> > +
> > +template<int N, class... Ts>
> > +using __const_type_pack_element_t = const __type_pack_element<N, Ts...>;
> > +
> > +using ty2 = __const_type_pack_element_t<2, int, char, long>;
> > +using ty2 = const long;
> > +
> > +template<class T> struct A { };
> > +using ty3 = __type_pack_element<3, int, int, int, A<int>>;
> > +using ty3 = A<int>;
> > diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> > b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> > new file mode 100644
> > index 00000000000..363cfa92514
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> > @@ -0,0 +1,14 @@
> > +// { dg-do compile { target c++11 } }
> > +
> > +int p;
> > +
> > +using type = __type_pack_element<&p, int>; // { dg-error "not an integral
> > constant" }
> > +using type = __type_pack_element<1, int>; // { dg-error "out of range" }
> > +using type = __type_pack_element<-1, int>; // { dg-error "negative" }
> > +using type = __type_pack_element<2, int, char>; // { dg-error "out of
> > range" }
> > +
> > +template<int N, class... Ts>
> > +using __type_pack_element_t = __type_pack_element<N, Ts...>;
> > +// { dg-error "out of range" "" { target *-*-* } .-1 }
> > +
> > +using type = __type_pack_element_t<3, int, char, long>; // { dg-message
> > "here" }
> > diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element3.C
> > b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
> > new file mode 100644
> > index 00000000000..269f84f464f
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
> > @@ -0,0 +1,22 @@
> > +// { dg-do compile { target c++11 } }
> > +
> > +template<class T, T N, class... Ts, class = __type_pack_element<N, Ts...>>
> > +constexpr int f(int) { return 1; }
> > +
> > +template<class T, T N, class... Ts>
> > +constexpr int f(...) { return 2; };
> > +
> > +int p;
> > +
> > +static_assert(f<int, 0, void, char>(0) == 1, "");
> > +static_assert(f<int, 1, void, char>(0) == 1, "");
> > +static_assert(f<int, 2, void, char>(0) == 2, "");
> > +static_assert(f<int*, &p, void, char>(0) == 2, "");
> > +
> > +template<class T, class U> struct A;
> > +template<class T> struct A<T, __type_pack_element<sizeof(T), void, long>> {
> > };
> > +template struct A<char, long>;
> > +
> > +template<class T, class U> struct B;
> > +template<class T> struct B<T, __type_pack_element<0, T, short>> { };
> > +template struct B<int, int>;
> > diff --git a/libstdc++-v3/include/bits/utility.h
> > b/libstdc++-v3/include/bits/utility.h
> > index 6a192e27836..5d524d33b4c 100644
> > --- a/libstdc++-v3/include/bits/utility.h
> > +++ b/libstdc++-v3/include/bits/utility.h
> > @@ -224,6 +224,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >   #endif // C++17
> >   #endif // C++14
> >   +#if __has_builtin(__type_pack_element)
> > +  template<size_t _Np, typename... _Types>
> > +    struct _Nth_type
> > +    { using type = __type_pack_element<_Np, _Types...>; };
> > +#else
> >     template<size_t _Np, typename... _Types>
> >       struct _Nth_type
> >       { };
> > @@ -262,6 +267,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >       struct _Nth_type<1, _Tp0, _Tp1, _Tp2, _Rest...>
> >       { using type = _Tp1; };
> >   #endif
> > +#endif
> >     _GLIBCXX_END_NAMESPACE_VERSION
> >   } // namespace
> 
> 


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2023-01-25 20:35         ` Patrick Palka
@ 2023-01-26 17:47           ` Jason Merrill
  2023-04-11 14:21             ` Patrick Palka
  0 siblings, 1 reply; 14+ messages in thread
From: Jason Merrill @ 2023-01-26 17:47 UTC (permalink / raw)
  To: Patrick Palka; +Cc: Jonathan Wakely, libstdc++, gcc-patches, Marek Polacek

On 1/25/23 15:35, Patrick Palka wrote:
> On Tue, 17 Jan 2023, Jason Merrill wrote:
> 
>> On 1/9/23 14:25, Patrick Palka via Gcc-patches wrote:
>>> On Mon, 9 Jan 2023, Patrick Palka wrote:
>>>
>>>> On Wed, 5 Oct 2022, Patrick Palka wrote:
>>>>
>>>>> On Thu, 7 Jul 2022, Jonathan Wakely via Gcc-patches wrote:
>>>>>
>>>>>> This adds a new built-in to replace the recursive class template
>>>>>> instantiations done by traits such as std::tuple_element and
>>>>>> std::variant_alternative. The purpose is to select the Nth type from a
>>>>>> list of types, e.g. __builtin_type_pack_element(1, char, int, float)
>>>>>> is
>>>>>> int.
>>>>>>
>>>>>> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
>>>>>> the compilation time is reduced by more than 90% and the memory  used
>>>>>> by
>>>>>> the compiler is reduced by 97%. In realistic examples the gains will
>>>>>> be
>>>>>> much smaller, but still relevant.
>>>>>>
>>>>>> Clang has a similar built-in, __type_pack_element<N, T...>, but that's
>>>>>> a
>>>>>> "magic template" built-in using <> syntax, which GCC doesn't support.
>>>>>> So
>>>>>> this provides an equivalent feature, but as a built-in function using
>>>>>> parens instead of <>. I don't really like the name "type pack element"
>>>>>> (it gives you an element from a pack of types) but the
>>>>>> semi-consistency
>>>>>> with Clang seems like a reasonable argument in favour of keeping the
>>>>>> name. I'd be open to alternative names though, e.g. __builtin_nth_type
>>>>>> or __builtin_type_at_index.
>>>>>
>>>>> Rather than giving the trait a different name from __type_pack_element,
>>>>> I wonder if we could just special case cp_parser_trait to expect <>
>>>>> instead of parens for this trait?
>>>>>
>>>>> Btw the frontend recently got a generic TRAIT_TYPE tree code, which gets
>>>>> rid of much of the boilerplate of adding a new type-yielding built-in
>>>>> trait, see e.g. cp-trait.def.
>>>>
>>>> Here's a tested patch based on Jonathan's original patch that implements
>>>> the built-in in terms of TRAIT_TYPE, names it __type_pack_element
>>>> instead of __builtin_type_pack_element, and treats invocations of it
>>>> like a template-id instead of a call (to match Clang).
>>>>
>>>> -- >8 --
>>>>
>>>> Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
>>>>
>>>> This adds a new built-in to replace the recursive class template
>>>> instantiations done by traits such as std::tuple_element and
>>>> std::variant_alternative.  The purpose is to select the Nth type from a
>>>> list of types, e.g. __type_pack_element<1, char, int, float> is int.
>>>> We implement it as a special kind of TRAIT_TYPE.
>>>>
>>>> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
>>>> the compilation time is reduced by more than 90% and the memory  used by
>>>> the compiler is reduced by 97%.  In realistic examples the gains will be
>>>> much smaller, but still relevant.
>>>>
>>>> Unlike the other built-in traits, __type_pack_element uses template-id
>>>> syntax instead of call syntax and is SFINAE-enabled, matching Clang's
>>>> implementation.  And like the other built-in traits, it's not mangleable
>>>> so we can't use it directly in function signatures.
>>>>
>>>> Some caveats:
>>>>
>>>>     * Clang's version of the built-in seems to act like a "magic template"
>>>>       that can e.g. be used as a template template argument.  For
>>>> simplicity
>>>>       we implement it in a more ad-hoc way.
>>>>     * Our parsing of the <>'s in __type_pack_element<...> is currently
>>>>       rudimentary and doesn't try to disambiguate a trailing >> vs > >
>>>>       as cp_parser_enclosed_template_argument_list does.
>>>
>>> Hmm, this latter caveat turns out to be inconvenient (for code such as
>>> type_pack_element3.C) and admits an easy workaround inspired by what
>>> cp_parser_enclosed_template_argument_list does.
>>>
>>> v2: Consider the >> in __type_pack_element<0, int, char>> to be two >'s.
>>>       Handle non-type TRAIT_TYPE_TYPE1 in strip_typedefs (for sake of
>>>       CPTK_TYPE_PACK_ELEMENT).
>>
>> Why not use cp_parser_enclosed_template_argument_list directly?
> 
> If we used cp_parser_enclosed_template_argument_list we would then need
> to convert the returned TREE_VEC into a TREE_LIST and also diagnose
> argument kind mismatches (i.e. verify the first argument is an
> expression and the rest are types).  It seemed like more complexity
> overall then just duplicating the >> splitting logic, but I can do that
> if you prefer?

I think I would prefer that, parser stuff can be pretty subtle.

Instead of turning the TREE_VEC into a TREE_LIST, we could handle 
TREE_VEC as a trait operand?

>>> -- >8 --
>>>
>>> Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
>>>
>>> This adds a new built-in to replace the recursive class template
>>> instantiations done by traits such as std::tuple_element and
>>> std::variant_alternative.  The purpose is to select the Nth type from a
>>> list of types, e.g. __type_pack_element<1, char, int, float> is int.
>>> We implement it as a special kind of TRAIT_TYPE.
>>>
>>> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
>>> the compilation time is reduced by more than 90% and the memory  used by
>>> the compiler is reduced by 97%.  In realistic examples the gains will be
>>> much smaller, but still relevant.
>>>
>>> Unlike the other built-in traits, __type_pack_element uses template-id
>>> syntax instead of call syntax and is SFINAE-enabled, matching Clang's
>>> implementation.  And like the other built-in traits, it's not mangleable
>>> so we can't use it directly in function signatures.
>>>
>>> N.B. Clang seems to implement __type_pack_element as a first-class
>>> template that can e.g. be used as a template template argument.  For
>>> simplicity we implement it in a more ad-hoc way.
>>>
>>> Co-authored-by: Jonathan Wakely <jwakely@redhat.com>
>>>
>>> 	PR c++/100157
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* cp-trait.def (TYPE_PACK_ELEMENT): Define.
>>> 	* cp-tree.h (finish_trait_type): Add complain parameter.
>>> 	* cxx-pretty-print.cc (pp_cxx_trait): Handle
>>> 	CPTK_TYPE_PACK_ELEMENT.
>>> 	* parser.cc (cp_parser_constant_expression): Document default
>>> 	arguments.
>>> 	(cp_parser_trait): Handle CPTK_TYPE_PACK_ELEMENT.  Pass
>>> 	tf_warning_or_error to finish_trait_type.
>>> 	* pt.cc (tsubst) <case TRAIT_TYPE>: Handle
>>> 	CPTK_TYPE_PACK_ELEMENT.
>>> 	* semantics.cc (finish_type_pack_element): Define.
>>> 	(finish_trait_type): Add complain parameter.  Handle
>>> 	CPTK_TYPE_PACK_ELEMENT.
>>> 	* tree.cc (strip_typedefs): Handle non-type TRAIT_TYPE_TYPE1.
>>> 	Pass tf_warning_or_error to finish_trait_type.
>>> 	* typeck.cc (structural_comptypes) <case TRAIT_TYPE>: Use
>>> 	cp_tree_equal instead of same_type_p for TRAIT_TYPE_TYPE1.
>>>
>>> libstdc++-v3/ChangeLog:
>>>
>>> 	* include/bits/utility.h (_Nth_type): Conditionally define in
>>> 	terms of __type_pack_element if available.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	* g++.dg/ext/type_pack_element1.C: New test.
>>> 	* g++.dg/ext/type_pack_element2.C: New test.
>>> 	* g++.dg/ext/type_pack_element3.C: New test.
>>> ---
>>>    gcc/cp/cp-trait.def                           |  1 +
>>>    gcc/cp/cp-tree.h                              |  2 +-
>>>    gcc/cp/cxx-pretty-print.cc                    | 17 +++++--
>>>    gcc/cp/parser.cc                              | 46 +++++++++++++++----
>>>    gcc/cp/pt.cc                                  |  8 +++-
>>>    gcc/cp/semantics.cc                           | 39 +++++++++++++++-
>>>    gcc/cp/tree.cc                                | 10 ++--
>>>    gcc/cp/typeck.cc                              |  2 +-
>>>    gcc/testsuite/g++.dg/ext/type_pack_element1.C | 19 ++++++++
>>>    gcc/testsuite/g++.dg/ext/type_pack_element2.C | 14 ++++++
>>>    gcc/testsuite/g++.dg/ext/type_pack_element3.C | 22 +++++++++
>>>    libstdc++-v3/include/bits/utility.h           |  6 +++
>>>    12 files changed, 165 insertions(+), 21 deletions(-)
>>>    create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element1.C
>>>    create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element2.C
>>>    create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element3.C
>>>
>>> diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
>>> index 823899a26c5..63f6b101eeb 100644
>>> --- a/gcc/cp/cp-trait.def
>>> +++ b/gcc/cp/cp-trait.def
>>> @@ -89,6 +89,7 @@ DEFTRAIT_TYPE (REMOVE_CV, "__remove_cv", 1)
>>>    DEFTRAIT_TYPE (REMOVE_REFERENCE, "__remove_reference", 1)
>>>    DEFTRAIT_TYPE (REMOVE_CVREF, "__remove_cvref", 1)
>>>    DEFTRAIT_TYPE (UNDERLYING_TYPE,  "__underlying_type", 1)
>>> +DEFTRAIT_TYPE (TYPE_PACK_ELEMENT, "__type_pack_element", -1)
>>>      /* These traits yield a type pack, not a type, and are represented by
>>>       cp_parser_trait as a special BASES tree instead of a TRAIT_TYPE tree.
>>> */
>>> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>>> index 1f4967c2ba0..f3cc2c5874c 100644
>>> --- a/gcc/cp/cp-tree.h
>>> +++ b/gcc/cp/cp-tree.h
>>> @@ -7764,7 +7764,7 @@ extern tree finish_decltype_type                (tree,
>>> bool, tsubst_flags_t);
>>>    extern tree fold_builtin_is_corresponding_member (location_t, int, tree
>>> *);
>>>    extern tree fold_builtin_is_pointer_inverconvertible_with_class
>>> (location_t, int, tree *);
>>>    extern tree finish_trait_expr			(location_t, enum
>>> cp_trait_kind, tree, tree);
>>> -extern tree finish_trait_type			(enum cp_trait_kind,
>>> tree, tree);
>>> +extern tree finish_trait_type			(enum cp_trait_kind,
>>> tree, tree, tsubst_flags_t);
>>>    extern tree build_lambda_expr                   (void);
>>>    extern tree build_lambda_object			(tree);
>>>    extern tree begin_lambda_type                   (tree);
>>> diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
>>> index 8ca1b8f234a..07a0406e491 100644
>>> --- a/gcc/cp/cxx-pretty-print.cc
>>> +++ b/gcc/cp/cxx-pretty-print.cc
>>> @@ -2625,8 +2625,16 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
>>>    #undef DEFTRAIT
>>>        }
>>>    -  pp_cxx_left_paren (pp);
>>> -  pp->type_id (type1);
>>> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
>>> +    {
>>> +      pp_cxx_begin_template_argument_list (pp);
>>> +      pp->expression (type1);
>>> +    }
>>> +  else
>>> +    {
>>> +      pp_cxx_left_paren (pp);
>>> +      pp->type_id (type1);
>>> +    }
>>>      if (type2)
>>>        {
>>>          if (TREE_CODE (type2) != TREE_LIST)
>>> @@ -2641,7 +2649,10 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
>>>    	    pp->type_id (TREE_VALUE (arg));
>>>    	  }
>>>        }
>>> -  pp_cxx_right_paren (pp);
>>> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
>>> +    pp_cxx_end_template_argument_list (pp);
>>> +  else
>>> +    pp_cxx_right_paren (pp);
>>>    }
>>>      // requires-clause:
>>> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
>>> index 8b1658decba..0a88d83d955 100644
>>> --- a/gcc/cp/parser.cc
>>> +++ b/gcc/cp/parser.cc
>>> @@ -10683,9 +10683,9 @@ cp_parser_expression (cp_parser* parser, cp_id_kind
>>> * pidk,
>>>      static cp_expr
>>>    cp_parser_constant_expression (cp_parser* parser,
>>> -			       int allow_non_constant_p,
>>> -			       bool *non_constant_p,
>>> -			       bool strict_p)
>>> +			       int allow_non_constant_p /* = 0 */,
>>> +			       bool *non_constant_p /* = NULL */,
>>> +			       bool strict_p /* = false */)
>>>    {
>>>      bool saved_integral_constant_expression_p;
>>>      bool saved_allow_non_integral_constant_expression_p;
>>> @@ -10912,12 +10912,25 @@ cp_parser_trait (cp_parser* parser, enum rid
>>> keyword)
>>>      cp_lexer_consume_token (parser->lexer);
>>>        matching_parens parens;
>>> -  parens.require_open (parser);
>>>    -  {
>>> -    type_id_in_expr_sentinel s (parser);
>>> -    type1 = cp_parser_type_id (parser);
>>> -  }
>>> +  /* Unlike the other built-in traits, __type_pack_element is unique in
>>> that
>>> +     it takes an expression as its first argument and it uses template-id
>>> +     syntax instead of function call syntax (for compatibility with Clang).
>>> +     We special case these properties of __type_pack_element here and
>>> elsewhere.
>>> +
>>> +     TODO: Generalize the trait infrastructure so that __type_pack_element
>>> +     is no longer a special case.  */
>>> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
>>> +    {
>>> +      cp_parser_require (parser, CPP_LESS, RT_LESS);
>>> +      type1 = cp_parser_constant_expression (parser);
>>> +    }
>>> +  else
>>> +    {
>>> +      parens.require_open (parser);
>>> +      type_id_in_expr_sentinel s (parser);
>>> +      type1 = cp_parser_type_id (parser);
>>> +    }
>>>        if (type1 == error_mark_node)
>>>        return error_mark_node;
>>> @@ -10953,7 +10966,20 @@ cp_parser_trait (cp_parser* parser, enum rid
>>> keyword)
>>>        }
>>>        location_t finish_loc = cp_lexer_peek_token (parser->lexer)->location;
>>> -  parens.require_close (parser);
>>> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
>>> +    {
>>> +      /* As in cp_parser_enclosed_template_argument_list, a trailing
>>> +	 '>>' here is considered to be two separate '>' tokens.  */
>>> +      if (cp_lexer_next_token_is (parser->lexer, CPP_RSHIFT))
>>> +	{
>>> +	  cp_token *token = cp_lexer_peek_token (parser->lexer);
>>> +	  token->type = CPP_GREATER;
>>> +	}
>>> +      else
>>> +	cp_parser_require (parser, CPP_GREATER, RT_GREATER);
>>> +    }
>>> +  else
>>> +    parens.require_close (parser);
>>>        /* Construct a location of the form:
>>>           __is_trivially_copyable(_Tp)
>>> @@ -10971,7 +10997,7 @@ cp_parser_trait (cp_parser* parser, enum rid
>>> keyword)
>>>          return cp_expr (finish_bases (type1, true), trait_loc);
>>>        default:
>>>          if (type)
>>> -	return finish_trait_type (kind, type1, type2);
>>> +	return finish_trait_type (kind, type1, type2, tf_warning_or_error);
>>>          else
>>>    	return finish_trait_expr (trait_loc, kind, type1, type2);
>>>        }
>>> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
>>> index cbe5898b553..2e83970a252 100644
>>> --- a/gcc/cp/pt.cc
>>> +++ b/gcc/cp/pt.cc
>>> @@ -16576,9 +16576,13 @@ tsubst (tree t, tree args, tsubst_flags_t complain,
>>> tree in_decl)
>>>          case TRAIT_TYPE:
>>>          {
>>> -	tree type1 = tsubst (TRAIT_TYPE_TYPE1 (t), args, complain, in_decl);
>>> +	tree type1 = TRAIT_TYPE_TYPE1 (t);
>>> +	if (TRAIT_TYPE_KIND (t) != CPTK_TYPE_PACK_ELEMENT)
>>> +	  type1 = tsubst (type1, args, complain, in_decl);
>>> +	else
>>> +	  type1 = tsubst_copy_and_build (type1, args, complain, in_decl);
>>>    	tree type2 = tsubst (TRAIT_TYPE_TYPE2 (t), args, complain, in_decl);
>>> -	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
>>> +	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2,
>>> complain);
>>>    	return cp_build_qualified_type (type,
>>>    					cp_type_quals (t) | cp_type_quals
>>> (type),
>>>    					complain | tf_ignore_bad_quals);
>>> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
>>> index ef5bf2430b1..af9edf59b3b 100644
>>> --- a/gcc/cp/semantics.cc
>>> +++ b/gcc/cp/semantics.cc
>>> @@ -4457,6 +4457,36 @@ finish_underlying_type (tree type)
>>>      return underlying_type;
>>>    }
>>>    +/* 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)
>>> +{
>>> +  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");
>>> +      return error_mark_node;
>>> +    }
>>> +  HOST_WIDE_INT val = tree_to_shwi (idx);
>>> +  if (val < 0)
>>> +    {
>>> +      if (complain & tf_error)
>>> +	error ("%<__type_pack_element%> index is negative");
>>> +      return error_mark_node;
>>> +    }
>>> +  tree result = chain_index (val, types);
>>> +  if (!result)
>>> +    {
>>> +      if (complain & tf_error)
>>> +	error ("%<__type_pack_element%> index is out of range");
>>> +      return error_mark_node;
>>> +    }
>>> +  return TREE_VALUE (result);
>>> +}
>>> +
>>>    /* Implement the __direct_bases keyword: Return the direct base classes
>>>       of type.  */
>>>    @@ -12213,7 +12243,8 @@ finish_trait_expr (location_t loc, cp_trait_kind
>>> kind, tree type1, tree type2)
>>>    /* Process a trait type.  */
>>>      tree
>>> -finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
>>> +finish_trait_type (cp_trait_kind kind, tree type1, tree type2,
>>> +		   tsubst_flags_t complain)
>>>    {
>>>      if (type1 == error_mark_node
>>>          || type2 == error_mark_node)
>>> @@ -12237,17 +12268,23 @@ finish_trait_type (cp_trait_kind kind, tree type1,
>>> tree type2)
>>>        {
>>>        case CPTK_UNDERLYING_TYPE:
>>>          return finish_underlying_type (type1);
>>> +
>>>        case CPTK_REMOVE_CV:
>>>          return cv_unqualified (type1);
>>> +
>>>        case CPTK_REMOVE_REFERENCE:
>>>          if (TYPE_REF_P (type1))
>>>    	type1 = TREE_TYPE (type1);
>>>          return type1;
>>> +
>>>        case CPTK_REMOVE_CVREF:
>>>          if (TYPE_REF_P (type1))
>>>    	type1 = TREE_TYPE (type1);
>>>          return cv_unqualified (type1);
>>>    +    case CPTK_TYPE_PACK_ELEMENT:
>>> +      return finish_type_pack_element (type1, type2, complain);
>>> +
>>>    #define DEFTRAIT_EXPR(CODE, NAME, ARITY) \
>>>        case CPTK_##CODE:
>>>    #include "cp-trait.def"
>>> diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
>>> index faf01616f87..d834a0fd38b 100644
>>> --- a/gcc/cp/tree.cc
>>> +++ b/gcc/cp/tree.cc
>>> @@ -1792,14 +1792,18 @@ strip_typedefs (tree t, bool *remove_attributes /* =
>>> NULL */,
>>>          break;
>>>        case TRAIT_TYPE:
>>>          {
>>> -	tree type1 = strip_typedefs (TRAIT_TYPE_TYPE1 (t),
>>> -				     remove_attributes, flags);
>>> +	tree type1 = TRAIT_TYPE_TYPE1 (t);
>>> +	if (TYPE_P (type1))
>>> +	  type1 = strip_typedefs (type1, remove_attributes, flags);
>>> +	else
>>> +	  type1 = strip_typedefs_expr (type1, remove_attributes, flags);
>>>    	tree type2 = strip_typedefs (TRAIT_TYPE_TYPE2 (t),
>>>    				     remove_attributes, flags);
>>>    	if (type1 == TRAIT_TYPE_TYPE1 (t) && type2 == TRAIT_TYPE_TYPE2 (t))
>>>    	  result = NULL_TREE;
>>>    	else
>>> -	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
>>> +	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2,
>>> +				      tf_warning_or_error);
>>>          }
>>>          break;
>>>        case TYPE_PACK_EXPANSION:
>>> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
>>> index 69b1268cfec..f5548737e4c 100644
>>> --- a/gcc/cp/typeck.cc
>>> +++ b/gcc/cp/typeck.cc
>>> @@ -1632,7 +1632,7 @@ structural_comptypes (tree t1, tree t2, int strict)
>>>        case TRAIT_TYPE:
>>>          if (TRAIT_TYPE_KIND (t1) != TRAIT_TYPE_KIND (t2))
>>>    	return false;
>>> -      if (!same_type_p (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
>>> +      if (!cp_tree_equal (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
>>>    	  || !cp_tree_equal (TRAIT_TYPE_TYPE2 (t1), TRAIT_TYPE_TYPE2 (t2)))
>>>    	return false;
>>>          break;
>>> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element1.C
>>> b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
>>> new file mode 100644
>>> index 00000000000..46858555502
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
>>> @@ -0,0 +1,19 @@
>>> +// { dg-do compile { target c++11 } }
>>> +
>>> +using ty0 = __type_pack_element<0, int>;
>>> +using ty0 = __type_pack_element<0, int, char>;
>>> +using ty0 = int;
>>> +
>>> +using ty1 = __type_pack_element<1, int, char>;
>>> +using ty1 = __type_pack_element<(6 - 5) * 1, int, char>;
>>> +using ty1 = char;
>>> +
>>> +template<int N, class... Ts>
>>> +using __const_type_pack_element_t = const __type_pack_element<N, Ts...>;
>>> +
>>> +using ty2 = __const_type_pack_element_t<2, int, char, long>;
>>> +using ty2 = const long;
>>> +
>>> +template<class T> struct A { };
>>> +using ty3 = __type_pack_element<3, int, int, int, A<int>>;
>>> +using ty3 = A<int>;
>>> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element2.C
>>> b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
>>> new file mode 100644
>>> index 00000000000..363cfa92514
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
>>> @@ -0,0 +1,14 @@
>>> +// { dg-do compile { target c++11 } }
>>> +
>>> +int p;
>>> +
>>> +using type = __type_pack_element<&p, int>; // { dg-error "not an integral
>>> constant" }
>>> +using type = __type_pack_element<1, int>; // { dg-error "out of range" }
>>> +using type = __type_pack_element<-1, int>; // { dg-error "negative" }
>>> +using type = __type_pack_element<2, int, char>; // { dg-error "out of
>>> range" }
>>> +
>>> +template<int N, class... Ts>
>>> +using __type_pack_element_t = __type_pack_element<N, Ts...>;
>>> +// { dg-error "out of range" "" { target *-*-* } .-1 }
>>> +
>>> +using type = __type_pack_element_t<3, int, char, long>; // { dg-message
>>> "here" }
>>> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element3.C
>>> b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
>>> new file mode 100644
>>> index 00000000000..269f84f464f
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
>>> @@ -0,0 +1,22 @@
>>> +// { dg-do compile { target c++11 } }
>>> +
>>> +template<class T, T N, class... Ts, class = __type_pack_element<N, Ts...>>
>>> +constexpr int f(int) { return 1; }
>>> +
>>> +template<class T, T N, class... Ts>
>>> +constexpr int f(...) { return 2; };
>>> +
>>> +int p;
>>> +
>>> +static_assert(f<int, 0, void, char>(0) == 1, "");
>>> +static_assert(f<int, 1, void, char>(0) == 1, "");
>>> +static_assert(f<int, 2, void, char>(0) == 2, "");
>>> +static_assert(f<int*, &p, void, char>(0) == 2, "");
>>> +
>>> +template<class T, class U> struct A;
>>> +template<class T> struct A<T, __type_pack_element<sizeof(T), void, long>> {
>>> };
>>> +template struct A<char, long>;
>>> +
>>> +template<class T, class U> struct B;
>>> +template<class T> struct B<T, __type_pack_element<0, T, short>> { };
>>> +template struct B<int, int>;
>>> diff --git a/libstdc++-v3/include/bits/utility.h
>>> b/libstdc++-v3/include/bits/utility.h
>>> index 6a192e27836..5d524d33b4c 100644
>>> --- a/libstdc++-v3/include/bits/utility.h
>>> +++ b/libstdc++-v3/include/bits/utility.h
>>> @@ -224,6 +224,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>>    #endif // C++17
>>>    #endif // C++14
>>>    +#if __has_builtin(__type_pack_element)
>>> +  template<size_t _Np, typename... _Types>
>>> +    struct _Nth_type
>>> +    { using type = __type_pack_element<_Np, _Types...>; };
>>> +#else
>>>      template<size_t _Np, typename... _Types>
>>>        struct _Nth_type
>>>        { };
>>> @@ -262,6 +267,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>>        struct _Nth_type<1, _Tp0, _Tp1, _Tp2, _Rest...>
>>>        { using type = _Tp1; };
>>>    #endif
>>> +#endif
>>>      _GLIBCXX_END_NAMESPACE_VERSION
>>>    } // namespace
>>
>>
> 


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2023-01-26 17:47           ` Jason Merrill
@ 2023-04-11 14:21             ` Patrick Palka
  2023-04-18 19:09               ` Jason Merrill
  0 siblings, 1 reply; 14+ messages in thread
From: Patrick Palka @ 2023-04-11 14:21 UTC (permalink / raw)
  To: Jason Merrill
  Cc: Patrick Palka, Jonathan Wakely, libstdc++, gcc-patches, Marek Polacek

On Thu, 26 Jan 2023, Jason Merrill wrote:

> On 1/25/23 15:35, Patrick Palka wrote:
> > On Tue, 17 Jan 2023, Jason Merrill wrote:
> > 
> > > On 1/9/23 14:25, Patrick Palka via Gcc-patches wrote:
> > > > On Mon, 9 Jan 2023, Patrick Palka wrote:
> > > > 
> > > > > On Wed, 5 Oct 2022, Patrick Palka wrote:
> > > > > 
> > > > > > On Thu, 7 Jul 2022, Jonathan Wakely via Gcc-patches wrote:
> > > > > > 
> > > > > > > This adds a new built-in to replace the recursive class template
> > > > > > > instantiations done by traits such as std::tuple_element and
> > > > > > > std::variant_alternative. The purpose is to select the Nth type
> > > > > > > from a
> > > > > > > list of types, e.g. __builtin_type_pack_element(1, char, int,
> > > > > > > float)
> > > > > > > is
> > > > > > > int.
> > > > > > > 
> > > > > > > For a pathological example tuple_element_t<1000, tuple<2000
> > > > > > > types...>>
> > > > > > > the compilation time is reduced by more than 90% and the memory
> > > > > > > used
> > > > > > > by
> > > > > > > the compiler is reduced by 97%. In realistic examples the gains
> > > > > > > will
> > > > > > > be
> > > > > > > much smaller, but still relevant.
> > > > > > > 
> > > > > > > Clang has a similar built-in, __type_pack_element<N, T...>, but
> > > > > > > that's
> > > > > > > a
> > > > > > > "magic template" built-in using <> syntax, which GCC doesn't
> > > > > > > support.
> > > > > > > So
> > > > > > > this provides an equivalent feature, but as a built-in function
> > > > > > > using
> > > > > > > parens instead of <>. I don't really like the name "type pack
> > > > > > > element"
> > > > > > > (it gives you an element from a pack of types) but the
> > > > > > > semi-consistency
> > > > > > > with Clang seems like a reasonable argument in favour of keeping
> > > > > > > the
> > > > > > > name. I'd be open to alternative names though, e.g.
> > > > > > > __builtin_nth_type
> > > > > > > or __builtin_type_at_index.
> > > > > > 
> > > > > > Rather than giving the trait a different name from
> > > > > > __type_pack_element,
> > > > > > I wonder if we could just special case cp_parser_trait to expect <>
> > > > > > instead of parens for this trait?
> > > > > > 
> > > > > > Btw the frontend recently got a generic TRAIT_TYPE tree code, which
> > > > > > gets
> > > > > > rid of much of the boilerplate of adding a new type-yielding
> > > > > > built-in
> > > > > > trait, see e.g. cp-trait.def.
> > > > > 
> > > > > Here's a tested patch based on Jonathan's original patch that
> > > > > implements
> > > > > the built-in in terms of TRAIT_TYPE, names it __type_pack_element
> > > > > instead of __builtin_type_pack_element, and treats invocations of it
> > > > > like a template-id instead of a call (to match Clang).
> > > > > 
> > > > > -- >8 --
> > > > > 
> > > > > Subject: [PATCH] c++: Define built-in for std::tuple_element
> > > > > [PR100157]
> > > > > 
> > > > > This adds a new built-in to replace the recursive class template
> > > > > instantiations done by traits such as std::tuple_element and
> > > > > std::variant_alternative.  The purpose is to select the Nth type from
> > > > > a
> > > > > list of types, e.g. __type_pack_element<1, char, int, float> is int.
> > > > > We implement it as a special kind of TRAIT_TYPE.
> > > > > 
> > > > > For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> > > > > the compilation time is reduced by more than 90% and the memory  used
> > > > > by
> > > > > the compiler is reduced by 97%.  In realistic examples the gains will
> > > > > be
> > > > > much smaller, but still relevant.
> > > > > 
> > > > > Unlike the other built-in traits, __type_pack_element uses template-id
> > > > > syntax instead of call syntax and is SFINAE-enabled, matching Clang's
> > > > > implementation.  And like the other built-in traits, it's not
> > > > > mangleable
> > > > > so we can't use it directly in function signatures.
> > > > > 
> > > > > Some caveats:
> > > > > 
> > > > >     * Clang's version of the built-in seems to act like a "magic
> > > > > template"
> > > > >       that can e.g. be used as a template template argument.  For
> > > > > simplicity
> > > > >       we implement it in a more ad-hoc way.
> > > > >     * Our parsing of the <>'s in __type_pack_element<...> is currently
> > > > >       rudimentary and doesn't try to disambiguate a trailing >> vs > >
> > > > >       as cp_parser_enclosed_template_argument_list does.
> > > > 
> > > > Hmm, this latter caveat turns out to be inconvenient (for code such as
> > > > type_pack_element3.C) and admits an easy workaround inspired by what
> > > > cp_parser_enclosed_template_argument_list does.
> > > > 
> > > > v2: Consider the >> in __type_pack_element<0, int, char>> to be two >'s.
> > > >       Handle non-type TRAIT_TYPE_TYPE1 in strip_typedefs (for sake of
> > > >       CPTK_TYPE_PACK_ELEMENT).
> > > 
> > > Why not use cp_parser_enclosed_template_argument_list directly?
> > 
> > If we used cp_parser_enclosed_template_argument_list we would then need
> > to convert the returned TREE_VEC into a TREE_LIST and also diagnose
> > argument kind mismatches (i.e. verify the first argument is an
> > expression and the rest are types).  It seemed like more complexity
> > overall then just duplicating the >> splitting logic, but I can do that
> > if you prefer?
> 
> I think I would prefer that, parser stuff can be pretty subtle.
> 
> Instead of turning the TREE_VEC into a TREE_LIST, we could handle TREE_VEC as
> a trait operand?

Sorry for the late follow up...  Here's an updated patch that uses
cp_parser_enclosed_template_argument_list instead of copying the parsing
logic from there.  I put off handling TREE_VEC as a trait operand for
now.  We could convert all variadic traits to use TREE_VEC instead of
TREE_LIST at once in a followup patch.

Bootstrapped and regtested on x86_64-pc-linux-gnu, also tested against
libc++'s tuple/variant impl for good measure (which uses
__type_pack_element when available).

-- >8 --

Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]

This adds a new built-in to replace the recursive class template
instantiations done by traits such as std::tuple_element and
std::variant_alternative.  The purpose is to select the Nth type from a
list of types, e.g. __type_pack_element<1, char, int, float> is int.
We implement it as a special kind of TRAIT_TYPE.

For a pathological example tuple_element_t<1000, tuple<2000 types...>>
the compilation time is reduced by more than 90% and the memory  used by
the compiler is reduced by 97%.  In realistic examples the gains will be
much smaller, but still relevant.

Unlike the other built-in traits, __type_pack_element uses template-id
syntax instead of call syntax and is SFINAE-enabled, matching Clang's
implementation.  And like the other built-in traits, it's not mangleable
so we can't use it directly in function signatures.

N.B. Clang seems to implement __type_pack_element as a first-class
template that can e.g. be used as a template-template argument.  For
simplicity we implement it in a more ad-hoc way.

Co-authored-by: Jonathan Wakely <jwakely@redhat.com>

	PR c++/100157

gcc/cp/ChangeLog:

	* cp-trait.def (TYPE_PACK_ELEMENT): Define.
	* cp-tree.h (finish_trait_type): Add complain parameter.
	* cxx-pretty-print.cc (pp_cxx_trait): Handle
	CPTK_TYPE_PACK_ELEMENT.
	* parser.cc (cp_parser_constant_expression): Document default
	arguments.
	(cp_parser_trait): Handle CPTK_TYPE_PACK_ELEMENT.  Pass
	tf_warning_or_error to finish_trait_type.
	* pt.cc (tsubst) <case TRAIT_TYPE>: Handle non-type first
	argument.  Pass complain to finish_trait_type.
	* semantics.cc (finish_type_pack_element): Define.
	(finish_trait_type): Add complain parameter.  Handle
	CPTK_TYPE_PACK_ELEMENT.
	* tree.cc (strip_typedefs): Handle non-type first argument.
	Pass tf_warning_or_error to finish_trait_type.
	* typeck.cc (structural_comptypes) <case TRAIT_TYPE>: Use
	cp_tree_equal instead of same_type_p for the first argument.

libstdc++-v3/ChangeLog:

	* include/bits/utility.h (_Nth_type): Conditionally define in
	terms of __type_pack_element if available.
	* testsuite/20_util/tuple/element_access/get_neg.cc: Prune
	additional errors from the new built-in.

gcc/testsuite/ChangeLog:

	* g++.dg/ext/type_pack_element1.C: New test.
	* g++.dg/ext/type_pack_element2.C: New test.
	* g++.dg/ext/type_pack_element3.C: New test.
---
 gcc/cp/cp-trait.def                           |  1 +
 gcc/cp/cp-tree.h                              |  2 +-
 gcc/cp/cxx-pretty-print.cc                    | 21 ++++++---
 gcc/cp/parser.cc                              | 45 ++++++++++++++++---
 gcc/cp/pt.cc                                  |  8 +++-
 gcc/cp/semantics.cc                           | 39 +++++++++++++++-
 gcc/cp/tree.cc                                | 10 +++--
 gcc/cp/typeck.cc                              |  2 +-
 gcc/testsuite/g++.dg/ext/type_pack_element1.C | 19 ++++++++
 gcc/testsuite/g++.dg/ext/type_pack_element2.C | 14 ++++++
 gcc/testsuite/g++.dg/ext/type_pack_element3.C | 22 +++++++++
 libstdc++-v3/include/bits/utility.h           |  6 +++
 .../20_util/tuple/element_access/get_neg.cc   |  1 +
 13 files changed, 170 insertions(+), 20 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element1.C
 create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element2.C
 create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element3.C

diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
index bac593c0094..8b7fece0cc8 100644
--- a/gcc/cp/cp-trait.def
+++ b/gcc/cp/cp-trait.def
@@ -91,6 +91,7 @@ DEFTRAIT_TYPE (REMOVE_CV, "__remove_cv", 1)
 DEFTRAIT_TYPE (REMOVE_REFERENCE, "__remove_reference", 1)
 DEFTRAIT_TYPE (REMOVE_CVREF, "__remove_cvref", 1)
 DEFTRAIT_TYPE (UNDERLYING_TYPE,  "__underlying_type", 1)
+DEFTRAIT_TYPE (TYPE_PACK_ELEMENT, "__type_pack_element", -1)
 
 /* These traits yield a type pack, not a type, and are represented by
    cp_parser_trait as a special BASES tree instead of a TRAIT_TYPE tree.  */
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 622752ae4e6..1c897c2ce8f 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7761,7 +7761,7 @@ extern tree finish_decltype_type                (tree, bool, tsubst_flags_t);
 extern tree fold_builtin_is_corresponding_member (location_t, int, tree *);
 extern tree fold_builtin_is_pointer_inverconvertible_with_class (location_t, int, tree *);
 extern tree finish_trait_expr			(location_t, enum cp_trait_kind, tree, tree);
-extern tree finish_trait_type			(enum cp_trait_kind, tree, tree);
+extern tree finish_trait_type			(enum cp_trait_kind, tree, tree, tsubst_flags_t);
 extern tree build_lambda_expr                   (void);
 extern tree build_lambda_object			(tree);
 extern tree begin_lambda_type                   (tree);
diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
index 7f4556d0da2..c33919873f1 100644
--- a/gcc/cp/cxx-pretty-print.cc
+++ b/gcc/cp/cxx-pretty-print.cc
@@ -2625,11 +2625,19 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
 #undef DEFTRAIT
     }
 
-  pp_cxx_left_paren (pp);
-  if (TYPE_P (type1))
-    pp->type_id (type1);
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    {
+      pp_cxx_begin_template_argument_list (pp);
+      pp->expression (type1);
+    }
   else
-    pp->expression (type1);
+    {
+      pp_cxx_left_paren (pp);
+      if (TYPE_P (type1))
+	pp->type_id (type1);
+      else
+	pp->expression (type1);
+    }
   if (type2)
     {
       if (TREE_CODE (type2) != TREE_LIST)
@@ -2644,7 +2652,10 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
 	    pp->type_id (TREE_VALUE (arg));
 	  }
     }
-  pp_cxx_right_paren (pp);
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    pp_cxx_end_template_argument_list (pp);
+  else
+    pp_cxx_right_paren (pp);
 }
 
 // requires-clause:
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index a6341b98af2..281292b462d 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -10730,9 +10730,9 @@ cp_parser_expression (cp_parser* parser, cp_id_kind * pidk,
 
 static cp_expr
 cp_parser_constant_expression (cp_parser* parser,
-			       int allow_non_constant_p,
-			       bool *non_constant_p,
-			       bool strict_p)
+			       int allow_non_constant_p /* = 0 */,
+			       bool *non_constant_p /* = NULL */,
+			       bool strict_p /* = false */)
 {
   bool saved_integral_constant_expression_p;
   bool saved_allow_non_integral_constant_expression_p;
@@ -10959,10 +10959,10 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
   cp_lexer_consume_token (parser->lexer);
 
   matching_parens parens;
-  parens.require_open (parser);
 
   if (kind == CPTK_IS_DEDUCIBLE)
     {
+      parens.require_open (parser);
       const cp_token* token = cp_lexer_peek_token (parser->lexer);
       type1 = cp_parser_id_expression (parser,
 				       /*template_keyword_p=*/false,
@@ -10972,8 +10972,18 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
 				       /*optional_p=*/false);
       type1 = cp_parser_lookup_name_simple (parser, type1, token->location);
     }
+  else if (kind == CPTK_TYPE_PACK_ELEMENT)
+    {
+      /* __type_pack_element takes an expression as its first argument and uses
+	 template-id syntax instead of function call syntax (for consistency
+	 with Clang).  We special case these properties of __type_pack_element
+	 here and elsewhere.  */
+      cp_parser_require (parser, CPP_LESS, RT_LESS);
+      type1 = cp_parser_constant_expression (parser);
+    }
   else
     {
+      parens.require_open (parser);
       type_id_in_expr_sentinel s (parser);
       type1 = cp_parser_type_id (parser);
     }
@@ -10981,7 +10991,24 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
   if (type1 == error_mark_node)
     return error_mark_node;
 
-  if (binary)
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    {
+      cp_parser_require (parser, CPP_COMMA, RT_COMMA);
+      tree rest = cp_parser_enclosed_template_argument_list (parser);
+      for (tree elt : tree_vec_range (rest))
+	{
+	  if (!TYPE_P (elt))
+	    {
+	      error_at (cp_expr_loc_or_input_loc (elt),
+			"trailing argument to %<__type_pack_element%> "
+			"is not a type");
+	      return error_mark_node;
+	    }
+	  type2 = tree_cons (NULL_TREE, elt, type2);
+	}
+      type2 = nreverse (type2);
+    }
+  else if (binary)
     {
       cp_parser_require (parser, CPP_COMMA, RT_COMMA);
 
@@ -11012,7 +11039,11 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
     }
 
   location_t finish_loc = cp_lexer_peek_token (parser->lexer)->location;
-  parens.require_close (parser);
+  if (kind == CPTK_TYPE_PACK_ELEMENT)
+    /* cp_parser_enclosed_template_argument_list above already took care
+       of parsing closing '>'.  */;
+  else
+    parens.require_close (parser);
 
   /* Construct a location of the form:
        __is_trivially_copyable(_Tp)
@@ -11030,7 +11061,7 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
       return cp_expr (finish_bases (type1, true), trait_loc);
     default:
       if (type)
-	return finish_trait_type (kind, type1, type2);
+	return finish_trait_type (kind, type1, type2, tf_warning_or_error);
       else
 	return finish_trait_expr (trait_loc, kind, type1, type2);
     }
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 185e23f642d..dec410dbaba 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -16695,9 +16695,13 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 
     case TRAIT_TYPE:
       {
-	tree type1 = tsubst (TRAIT_TYPE_TYPE1 (t), args, complain, in_decl);
+	tree type1 = TRAIT_TYPE_TYPE1 (t);
+	if (TYPE_P (type1))
+	  type1 = tsubst (type1, args, complain, in_decl);
+	else
+	  type1 = tsubst_copy_and_build (type1, args, complain, in_decl);
 	tree type2 = tsubst (TRAIT_TYPE_TYPE2 (t), args, complain, in_decl);
-	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
+	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2, complain);
 	return cp_build_qualified_type (type,
 					cp_type_quals (t) | cp_type_quals (type),
 					complain | tf_ignore_bad_quals);
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 99a76e3ed65..74b852c500a 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -4470,6 +4470,36 @@ finish_underlying_type (tree type)
   return underlying_type;
 }
 
+/* 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)
+{
+  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");
+      return error_mark_node;
+    }
+  HOST_WIDE_INT val = tree_to_shwi (idx);
+  if (val < 0)
+    {
+      if (complain & tf_error)
+	error ("%<__type_pack_element%> index is negative");
+      return error_mark_node;
+    }
+  tree result = chain_index (val, types);
+  if (!result)
+    {
+      if (complain & tf_error)
+	error ("%<__type_pack_element%> index is out of range");
+      return error_mark_node;
+    }
+  return TREE_VALUE (result);
+}
+
 /* Implement the __direct_bases keyword: Return the direct base classes
    of type.  */
 
@@ -12240,7 +12270,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
 /* Process a trait type.  */
 
 tree
-finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
+finish_trait_type (cp_trait_kind kind, tree type1, tree type2,
+		   tsubst_flags_t complain)
 {
   if (type1 == error_mark_node
       || type2 == error_mark_node)
@@ -12264,17 +12295,23 @@ finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
     {
     case CPTK_UNDERLYING_TYPE:
       return finish_underlying_type (type1);
+
     case CPTK_REMOVE_CV:
       return cv_unqualified (type1);
+
     case CPTK_REMOVE_REFERENCE:
       if (TYPE_REF_P (type1))
 	type1 = TREE_TYPE (type1);
       return type1;
+
     case CPTK_REMOVE_CVREF:
       if (TYPE_REF_P (type1))
 	type1 = TREE_TYPE (type1);
       return cv_unqualified (type1);
 
+    case CPTK_TYPE_PACK_ELEMENT:
+      return finish_type_pack_element (type1, type2, complain);
+
 #define DEFTRAIT_EXPR(CODE, NAME, ARITY) \
     case CPTK_##CODE:
 #include "cp-trait.def"
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index 16b8fcb7d57..2c22fac17ee 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -1792,14 +1792,18 @@ strip_typedefs (tree t, bool *remove_attributes /* = NULL */,
       break;
     case TRAIT_TYPE:
       {
-	tree type1 = strip_typedefs (TRAIT_TYPE_TYPE1 (t),
-				     remove_attributes, flags);
+	tree type1 = TRAIT_TYPE_TYPE1 (t);
+	if (TYPE_P (type1))
+	  type1 = strip_typedefs (type1, remove_attributes, flags);
+	else
+	  type1 = strip_typedefs_expr (type1, remove_attributes, flags);
 	tree type2 = strip_typedefs (TRAIT_TYPE_TYPE2 (t),
 				     remove_attributes, flags);
 	if (type1 == TRAIT_TYPE_TYPE1 (t) && type2 == TRAIT_TYPE_TYPE2 (t))
 	  result = NULL_TREE;
 	else
-	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
+	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2,
+				      tf_warning_or_error);
       }
       break;
     case TYPE_PACK_EXPANSION:
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 8b60cbbc167..53ac925a092 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -1632,7 +1632,7 @@ structural_comptypes (tree t1, tree t2, int strict)
     case TRAIT_TYPE:
       if (TRAIT_TYPE_KIND (t1) != TRAIT_TYPE_KIND (t2))
 	return false;
-      if (!same_type_p (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
+      if (!cp_tree_equal (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
 	  || !cp_tree_equal (TRAIT_TYPE_TYPE2 (t1), TRAIT_TYPE_TYPE2 (t2)))
 	return false;
       break;
diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element1.C b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
new file mode 100644
index 00000000000..46858555502
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
@@ -0,0 +1,19 @@
+// { dg-do compile { target c++11 } }
+
+using ty0 = __type_pack_element<0, int>;
+using ty0 = __type_pack_element<0, int, char>;
+using ty0 = int;
+
+using ty1 = __type_pack_element<1, int, char>;
+using ty1 = __type_pack_element<(6 - 5) * 1, int, char>;
+using ty1 = char;
+
+template<int N, class... Ts>
+using __const_type_pack_element_t = const __type_pack_element<N, Ts...>;
+
+using ty2 = __const_type_pack_element_t<2, int, char, long>;
+using ty2 = const long;
+
+template<class T> struct A { };
+using ty3 = __type_pack_element<3, int, int, int, A<int>>;
+using ty3 = A<int>;
diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element2.C b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
new file mode 100644
index 00000000000..1bf77534097
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
@@ -0,0 +1,14 @@
+// { dg-do compile { target c++11 } }
+
+int p;
+
+using type = __type_pack_element<&p, int>;      // { dg-error "not an integral constant" }
+using type = __type_pack_element<1, int>;       // { dg-error "out of range" }
+using type = __type_pack_element<2, int, char>; // { dg-error "out of range" }
+using type = __type_pack_element<-1, int>;      // { dg-error "negative" }
+
+template<int N, class... Ts>
+using __type_pack_element_t = __type_pack_element<N, Ts...>;
+// { dg-error "out of range" "" { target *-*-* } .-1 }
+
+using type = __type_pack_element_t<3, int, char, long>; // { dg-message "here" }
diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element3.C b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
new file mode 100644
index 00000000000..269f84f464f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
@@ -0,0 +1,22 @@
+// { dg-do compile { target c++11 } }
+
+template<class T, T N, class... Ts, class = __type_pack_element<N, Ts...>>
+constexpr int f(int) { return 1; }
+
+template<class T, T N, class... Ts>
+constexpr int f(...) { return 2; };
+
+int p;
+
+static_assert(f<int, 0, void, char>(0) == 1, "");
+static_assert(f<int, 1, void, char>(0) == 1, "");
+static_assert(f<int, 2, void, char>(0) == 2, "");
+static_assert(f<int*, &p, void, char>(0) == 2, "");
+
+template<class T, class U> struct A;
+template<class T> struct A<T, __type_pack_element<sizeof(T), void, long>> { };
+template struct A<char, long>;
+
+template<class T, class U> struct B;
+template<class T> struct B<T, __type_pack_element<0, T, short>> { };
+template struct B<int, int>;
diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
index abaaae2dd33..4692aa0c9b0 100644
--- a/libstdc++-v3/include/bits/utility.h
+++ b/libstdc++-v3/include/bits/utility.h
@@ -224,6 +224,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif // C++17
 #endif // C++14
 
+#if __has_builtin(__type_pack_element)
+  template<size_t _Np, typename... _Types>
+    struct _Nth_type
+    { using type = __type_pack_element<_Np, _Types...>; };
+#else
   template<size_t _Np, typename... _Types>
     struct _Nth_type
     { };
@@ -262,6 +267,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     struct _Nth_type<1, _Tp0, _Tp1, _Tp2, _Rest...>
     { using type = _Tp1; };
 #endif
+#endif
 
 #if __cplusplus > 202002L
 #define __cpp_lib_ranges_zip 202110L // for <tuple> and <utility>
diff --git a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
index 9bce0b1caa2..dd0df9be1ea 100644
--- a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
@@ -61,3 +61,4 @@ test03()
 
 // { dg-error "tuple index must be in range" "" { target *-*-* } 0 }
 // { dg-prune-output "no type named 'type' in .*_Nth_type" }
+// { dg-prune-output "'__type_pack_element' index is out of range" }
-- 
2.40.0.315.g0607f793cb


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2023-04-11 14:21             ` Patrick Palka
@ 2023-04-18 19:09               ` Jason Merrill
  2023-04-18 19:23                 ` Patrick Palka
  0 siblings, 1 reply; 14+ messages in thread
From: Jason Merrill @ 2023-04-18 19:09 UTC (permalink / raw)
  To: Patrick Palka; +Cc: Jonathan Wakely, libstdc++, gcc-patches, Marek Polacek

On 4/11/23 10:21, Patrick Palka wrote:
> On Thu, 26 Jan 2023, Jason Merrill wrote:
> 
>> On 1/25/23 15:35, Patrick Palka wrote:
>>> On Tue, 17 Jan 2023, Jason Merrill wrote:
>>>
>>>> On 1/9/23 14:25, Patrick Palka via Gcc-patches wrote:
>>>>> On Mon, 9 Jan 2023, Patrick Palka wrote:
>>>>>
>>>>>> On Wed, 5 Oct 2022, Patrick Palka wrote:
>>>>>>
>>>>>>> On Thu, 7 Jul 2022, Jonathan Wakely via Gcc-patches wrote:
>>>>>>>
>>>>>>>> This adds a new built-in to replace the recursive class template
>>>>>>>> instantiations done by traits such as std::tuple_element and
>>>>>>>> std::variant_alternative. The purpose is to select the Nth type
>>>>>>>> from a
>>>>>>>> list of types, e.g. __builtin_type_pack_element(1, char, int,
>>>>>>>> float)
>>>>>>>> is
>>>>>>>> int.
>>>>>>>>
>>>>>>>> For a pathological example tuple_element_t<1000, tuple<2000
>>>>>>>> types...>>
>>>>>>>> the compilation time is reduced by more than 90% and the memory
>>>>>>>> used
>>>>>>>> by
>>>>>>>> the compiler is reduced by 97%. In realistic examples the gains
>>>>>>>> will
>>>>>>>> be
>>>>>>>> much smaller, but still relevant.
>>>>>>>>
>>>>>>>> Clang has a similar built-in, __type_pack_element<N, T...>, but
>>>>>>>> that's
>>>>>>>> a
>>>>>>>> "magic template" built-in using <> syntax, which GCC doesn't
>>>>>>>> support.
>>>>>>>> So
>>>>>>>> this provides an equivalent feature, but as a built-in function
>>>>>>>> using
>>>>>>>> parens instead of <>. I don't really like the name "type pack
>>>>>>>> element"
>>>>>>>> (it gives you an element from a pack of types) but the
>>>>>>>> semi-consistency
>>>>>>>> with Clang seems like a reasonable argument in favour of keeping
>>>>>>>> the
>>>>>>>> name. I'd be open to alternative names though, e.g.
>>>>>>>> __builtin_nth_type
>>>>>>>> or __builtin_type_at_index.
>>>>>>>
>>>>>>> Rather than giving the trait a different name from
>>>>>>> __type_pack_element,
>>>>>>> I wonder if we could just special case cp_parser_trait to expect <>
>>>>>>> instead of parens for this trait?
>>>>>>>
>>>>>>> Btw the frontend recently got a generic TRAIT_TYPE tree code, which
>>>>>>> gets
>>>>>>> rid of much of the boilerplate of adding a new type-yielding
>>>>>>> built-in
>>>>>>> trait, see e.g. cp-trait.def.
>>>>>>
>>>>>> Here's a tested patch based on Jonathan's original patch that
>>>>>> implements
>>>>>> the built-in in terms of TRAIT_TYPE, names it __type_pack_element
>>>>>> instead of __builtin_type_pack_element, and treats invocations of it
>>>>>> like a template-id instead of a call (to match Clang).
>>>>>>
>>>>>> -- >8 --
>>>>>>
>>>>>> Subject: [PATCH] c++: Define built-in for std::tuple_element
>>>>>> [PR100157]
>>>>>>
>>>>>> This adds a new built-in to replace the recursive class template
>>>>>> instantiations done by traits such as std::tuple_element and
>>>>>> std::variant_alternative.  The purpose is to select the Nth type from
>>>>>> a
>>>>>> list of types, e.g. __type_pack_element<1, char, int, float> is int.
>>>>>> We implement it as a special kind of TRAIT_TYPE.
>>>>>>
>>>>>> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
>>>>>> the compilation time is reduced by more than 90% and the memory  used
>>>>>> by
>>>>>> the compiler is reduced by 97%.  In realistic examples the gains will
>>>>>> be
>>>>>> much smaller, but still relevant.
>>>>>>
>>>>>> Unlike the other built-in traits, __type_pack_element uses template-id
>>>>>> syntax instead of call syntax and is SFINAE-enabled, matching Clang's
>>>>>> implementation.  And like the other built-in traits, it's not
>>>>>> mangleable
>>>>>> so we can't use it directly in function signatures.
>>>>>>
>>>>>> Some caveats:
>>>>>>
>>>>>>      * Clang's version of the built-in seems to act like a "magic
>>>>>> template"
>>>>>>        that can e.g. be used as a template template argument.  For
>>>>>> simplicity
>>>>>>        we implement it in a more ad-hoc way.
>>>>>>      * Our parsing of the <>'s in __type_pack_element<...> is currently
>>>>>>        rudimentary and doesn't try to disambiguate a trailing >> vs > >
>>>>>>        as cp_parser_enclosed_template_argument_list does.
>>>>>
>>>>> Hmm, this latter caveat turns out to be inconvenient (for code such as
>>>>> type_pack_element3.C) and admits an easy workaround inspired by what
>>>>> cp_parser_enclosed_template_argument_list does.
>>>>>
>>>>> v2: Consider the >> in __type_pack_element<0, int, char>> to be two >'s.
>>>>>        Handle non-type TRAIT_TYPE_TYPE1 in strip_typedefs (for sake of
>>>>>        CPTK_TYPE_PACK_ELEMENT).
>>>>
>>>> Why not use cp_parser_enclosed_template_argument_list directly?
>>>
>>> If we used cp_parser_enclosed_template_argument_list we would then need
>>> to convert the returned TREE_VEC into a TREE_LIST and also diagnose
>>> argument kind mismatches (i.e. verify the first argument is an
>>> expression and the rest are types).  It seemed like more complexity
>>> overall then just duplicating the >> splitting logic, but I can do that
>>> if you prefer?
>>
>> I think I would prefer that, parser stuff can be pretty subtle.
>>
>> Instead of turning the TREE_VEC into a TREE_LIST, we could handle TREE_VEC as
>> a trait operand?
> 
> Sorry for the late follow up...  Here's an updated patch that uses
> cp_parser_enclosed_template_argument_list instead of copying the parsing
> logic from there.  I put off handling TREE_VEC as a trait operand for
> now.  We could convert all variadic traits to use TREE_VEC instead of
> TREE_LIST at once in a followup patch.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, also tested against
> libc++'s tuple/variant impl for good measure (which uses
> __type_pack_element when available).
> 
> -- >8 --
> 
> Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
> 
> This adds a new built-in to replace the recursive class template
> instantiations done by traits such as std::tuple_element and
> std::variant_alternative.  The purpose is to select the Nth type from a
> list of types, e.g. __type_pack_element<1, char, int, float> is int.
> We implement it as a special kind of TRAIT_TYPE.
> 
> For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> the compilation time is reduced by more than 90% and the memory  used by
> the compiler is reduced by 97%.  In realistic examples the gains will be
> much smaller, but still relevant.
> 
> Unlike the other built-in traits, __type_pack_element uses template-id
> syntax instead of call syntax and is SFINAE-enabled, matching Clang's
> implementation.  And like the other built-in traits, it's not mangleable
> so we can't use it directly in function signatures.
> 
> N.B. Clang seems to implement __type_pack_element as a first-class
> template that can e.g. be used as a template-template argument.  For
> simplicity we implement it in a more ad-hoc way.
> 
> Co-authored-by: Jonathan Wakely <jwakely@redhat.com>
> 
> 	PR c++/100157
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-trait.def (TYPE_PACK_ELEMENT): Define.
> 	* cp-tree.h (finish_trait_type): Add complain parameter.
> 	* cxx-pretty-print.cc (pp_cxx_trait): Handle
> 	CPTK_TYPE_PACK_ELEMENT.
> 	* parser.cc (cp_parser_constant_expression): Document default
> 	arguments.
> 	(cp_parser_trait): Handle CPTK_TYPE_PACK_ELEMENT.  Pass
> 	tf_warning_or_error to finish_trait_type.
> 	* pt.cc (tsubst) <case TRAIT_TYPE>: Handle non-type first
> 	argument.  Pass complain to finish_trait_type.
> 	* semantics.cc (finish_type_pack_element): Define.
> 	(finish_trait_type): Add complain parameter.  Handle
> 	CPTK_TYPE_PACK_ELEMENT.
> 	* tree.cc (strip_typedefs): Handle non-type first argument.
> 	Pass tf_warning_or_error to finish_trait_type.
> 	* typeck.cc (structural_comptypes) <case TRAIT_TYPE>: Use
> 	cp_tree_equal instead of same_type_p for the first argument.
> 
> libstdc++-v3/ChangeLog:
> 
> 	* include/bits/utility.h (_Nth_type): Conditionally define in
> 	terms of __type_pack_element if available.
> 	* testsuite/20_util/tuple/element_access/get_neg.cc: Prune
> 	additional errors from the new built-in.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/ext/type_pack_element1.C: New test.
> 	* g++.dg/ext/type_pack_element2.C: New test.
> 	* g++.dg/ext/type_pack_element3.C: New test.
> ---
>   gcc/cp/cp-trait.def                           |  1 +
>   gcc/cp/cp-tree.h                              |  2 +-
>   gcc/cp/cxx-pretty-print.cc                    | 21 ++++++---
>   gcc/cp/parser.cc                              | 45 ++++++++++++++++---
>   gcc/cp/pt.cc                                  |  8 +++-
>   gcc/cp/semantics.cc                           | 39 +++++++++++++++-
>   gcc/cp/tree.cc                                | 10 +++--
>   gcc/cp/typeck.cc                              |  2 +-
>   gcc/testsuite/g++.dg/ext/type_pack_element1.C | 19 ++++++++
>   gcc/testsuite/g++.dg/ext/type_pack_element2.C | 14 ++++++
>   gcc/testsuite/g++.dg/ext/type_pack_element3.C | 22 +++++++++
>   libstdc++-v3/include/bits/utility.h           |  6 +++
>   .../20_util/tuple/element_access/get_neg.cc   |  1 +
>   13 files changed, 170 insertions(+), 20 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element1.C
>   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element2.C
>   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element3.C
> 
> diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
> index bac593c0094..8b7fece0cc8 100644
> --- a/gcc/cp/cp-trait.def
> +++ b/gcc/cp/cp-trait.def
> @@ -91,6 +91,7 @@ DEFTRAIT_TYPE (REMOVE_CV, "__remove_cv", 1)
>   DEFTRAIT_TYPE (REMOVE_REFERENCE, "__remove_reference", 1)
>   DEFTRAIT_TYPE (REMOVE_CVREF, "__remove_cvref", 1)
>   DEFTRAIT_TYPE (UNDERLYING_TYPE,  "__underlying_type", 1)
> +DEFTRAIT_TYPE (TYPE_PACK_ELEMENT, "__type_pack_element", -1)
>   
>   /* These traits yield a type pack, not a type, and are represented by
>      cp_parser_trait as a special BASES tree instead of a TRAIT_TYPE tree.  */
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 622752ae4e6..1c897c2ce8f 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7761,7 +7761,7 @@ extern tree finish_decltype_type                (tree, bool, tsubst_flags_t);
>   extern tree fold_builtin_is_corresponding_member (location_t, int, tree *);
>   extern tree fold_builtin_is_pointer_inverconvertible_with_class (location_t, int, tree *);
>   extern tree finish_trait_expr			(location_t, enum cp_trait_kind, tree, tree);
> -extern tree finish_trait_type			(enum cp_trait_kind, tree, tree);
> +extern tree finish_trait_type			(enum cp_trait_kind, tree, tree, tsubst_flags_t);
>   extern tree build_lambda_expr                   (void);
>   extern tree build_lambda_object			(tree);
>   extern tree begin_lambda_type                   (tree);
> diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
> index 7f4556d0da2..c33919873f1 100644
> --- a/gcc/cp/cxx-pretty-print.cc
> +++ b/gcc/cp/cxx-pretty-print.cc
> @@ -2625,11 +2625,19 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
>   #undef DEFTRAIT
>       }
>   
> -  pp_cxx_left_paren (pp);
> -  if (TYPE_P (type1))
> -    pp->type_id (type1);
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    {
> +      pp_cxx_begin_template_argument_list (pp);
> +      pp->expression (type1);
> +    }
>     else
> -    pp->expression (type1);
> +    {
> +      pp_cxx_left_paren (pp);
> +      if (TYPE_P (type1))
> +	pp->type_id (type1);
> +      else
> +	pp->expression (type1);
> +    }
>     if (type2)
>       {
>         if (TREE_CODE (type2) != TREE_LIST)
> @@ -2644,7 +2652,10 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
>   	    pp->type_id (TREE_VALUE (arg));
>   	  }
>       }
> -  pp_cxx_right_paren (pp);
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    pp_cxx_end_template_argument_list (pp);
> +  else
> +    pp_cxx_right_paren (pp);
>   }
>   
>   // requires-clause:
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index a6341b98af2..281292b462d 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -10730,9 +10730,9 @@ cp_parser_expression (cp_parser* parser, cp_id_kind * pidk,
>   
>   static cp_expr
>   cp_parser_constant_expression (cp_parser* parser,
> -			       int allow_non_constant_p,
> -			       bool *non_constant_p,
> -			       bool strict_p)
> +			       int allow_non_constant_p /* = 0 */,
> +			       bool *non_constant_p /* = NULL */,
> +			       bool strict_p /* = false */)
>   {
>     bool saved_integral_constant_expression_p;
>     bool saved_allow_non_integral_constant_expression_p;
> @@ -10959,10 +10959,10 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
>     cp_lexer_consume_token (parser->lexer);
>   
>     matching_parens parens;
> -  parens.require_open (parser);
>   
>     if (kind == CPTK_IS_DEDUCIBLE)
>       {
> +      parens.require_open (parser);
>         const cp_token* token = cp_lexer_peek_token (parser->lexer);
>         type1 = cp_parser_id_expression (parser,
>   				       /*template_keyword_p=*/false,
> @@ -10972,8 +10972,18 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
>   				       /*optional_p=*/false);
>         type1 = cp_parser_lookup_name_simple (parser, type1, token->location);
>       }
> +  else if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    {
> +      /* __type_pack_element takes an expression as its first argument and uses
> +	 template-id syntax instead of function call syntax (for consistency
> +	 with Clang).  We special case these properties of __type_pack_element
> +	 here and elsewhere.  */
> +      cp_parser_require (parser, CPP_LESS, RT_LESS);
> +      type1 = cp_parser_constant_expression (parser);
> +    }
>     else
>       {
> +      parens.require_open (parser);
>         type_id_in_expr_sentinel s (parser);
>         type1 = cp_parser_type_id (parser);
>       }
> @@ -10981,7 +10991,24 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
>     if (type1 == error_mark_node)
>       return error_mark_node;
>   
> -  if (binary)
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    {
> +      cp_parser_require (parser, CPP_COMMA, RT_COMMA);
> +      tree rest = cp_parser_enclosed_template_argument_list (parser);
> +      for (tree elt : tree_vec_range (rest))
> +	{
> +	  if (!TYPE_P (elt))
> +	    {
> +	      error_at (cp_expr_loc_or_input_loc (elt),
> +			"trailing argument to %<__type_pack_element%> "
> +			"is not a type");
> +	      return error_mark_node;
> +	    }
> +	  type2 = tree_cons (NULL_TREE, elt, type2);
> +	}
> +      type2 = nreverse (type2);
> +    }
> +  else if (binary)
>       {
>         cp_parser_require (parser, CPP_COMMA, RT_COMMA);
>   
> @@ -11012,7 +11039,11 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
>       }
>   
>     location_t finish_loc = cp_lexer_peek_token (parser->lexer)->location;
> -  parens.require_close (parser);
> +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> +    /* cp_parser_enclosed_template_argument_list above already took care
> +       of parsing closing '>'.  */;
> +  else
> +    parens.require_close (parser);
>   
>     /* Construct a location of the form:
>          __is_trivially_copyable(_Tp)
> @@ -11030,7 +11061,7 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
>         return cp_expr (finish_bases (type1, true), trait_loc);
>       default:
>         if (type)
> -	return finish_trait_type (kind, type1, type2);
> +	return finish_trait_type (kind, type1, type2, tf_warning_or_error);
>         else
>   	return finish_trait_expr (trait_loc, kind, type1, type2);
>       }
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 185e23f642d..dec410dbaba 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -16695,9 +16695,13 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
>   
>       case TRAIT_TYPE:
>         {
> -	tree type1 = tsubst (TRAIT_TYPE_TYPE1 (t), args, complain, in_decl);
> +	tree type1 = TRAIT_TYPE_TYPE1 (t);
> +	if (TYPE_P (type1))
> +	  type1 = tsubst (type1, args, complain, in_decl);
> +	else
> +	  type1 = tsubst_copy_and_build (type1, args, complain, in_decl);
>   	tree type2 = tsubst (TRAIT_TYPE_TYPE2 (t), args, complain, in_decl);

In the case of __builtin_type_pack_element where the original is 
something like T..., we could optimize even further and select the 
element without actually doing the expansion?

The patch is OK for trunk as is.

Jason

> -	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
> +	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2, complain);
>   	return cp_build_qualified_type (type,
>   					cp_type_quals (t) | cp_type_quals (type),
>   					complain | tf_ignore_bad_quals);
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index 99a76e3ed65..74b852c500a 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -4470,6 +4470,36 @@ finish_underlying_type (tree type)
>     return underlying_type;
>   }
>   
> +/* 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)
> +{
> +  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");
> +      return error_mark_node;
> +    }
> +  HOST_WIDE_INT val = tree_to_shwi (idx);
> +  if (val < 0)
> +    {
> +      if (complain & tf_error)
> +	error ("%<__type_pack_element%> index is negative");
> +      return error_mark_node;
> +    }
> +  tree result = chain_index (val, types);
> +  if (!result)
> +    {
> +      if (complain & tf_error)
> +	error ("%<__type_pack_element%> index is out of range");
> +      return error_mark_node;
> +    }
> +  return TREE_VALUE (result);
> +}
> +
>   /* Implement the __direct_bases keyword: Return the direct base classes
>      of type.  */
>   
> @@ -12240,7 +12270,8 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
>   /* Process a trait type.  */
>   
>   tree
> -finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
> +finish_trait_type (cp_trait_kind kind, tree type1, tree type2,
> +		   tsubst_flags_t complain)
>   {
>     if (type1 == error_mark_node
>         || type2 == error_mark_node)
> @@ -12264,17 +12295,23 @@ finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
>       {
>       case CPTK_UNDERLYING_TYPE:
>         return finish_underlying_type (type1);
> +
>       case CPTK_REMOVE_CV:
>         return cv_unqualified (type1);
> +
>       case CPTK_REMOVE_REFERENCE:
>         if (TYPE_REF_P (type1))
>   	type1 = TREE_TYPE (type1);
>         return type1;
> +
>       case CPTK_REMOVE_CVREF:
>         if (TYPE_REF_P (type1))
>   	type1 = TREE_TYPE (type1);
>         return cv_unqualified (type1);
>   
> +    case CPTK_TYPE_PACK_ELEMENT:
> +      return finish_type_pack_element (type1, type2, complain);
> +
>   #define DEFTRAIT_EXPR(CODE, NAME, ARITY) \
>       case CPTK_##CODE:
>   #include "cp-trait.def"
> diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> index 16b8fcb7d57..2c22fac17ee 100644
> --- a/gcc/cp/tree.cc
> +++ b/gcc/cp/tree.cc
> @@ -1792,14 +1792,18 @@ strip_typedefs (tree t, bool *remove_attributes /* = NULL */,
>         break;
>       case TRAIT_TYPE:
>         {
> -	tree type1 = strip_typedefs (TRAIT_TYPE_TYPE1 (t),
> -				     remove_attributes, flags);
> +	tree type1 = TRAIT_TYPE_TYPE1 (t);
> +	if (TYPE_P (type1))
> +	  type1 = strip_typedefs (type1, remove_attributes, flags);
> +	else
> +	  type1 = strip_typedefs_expr (type1, remove_attributes, flags);
>   	tree type2 = strip_typedefs (TRAIT_TYPE_TYPE2 (t),
>   				     remove_attributes, flags);
>   	if (type1 == TRAIT_TYPE_TYPE1 (t) && type2 == TRAIT_TYPE_TYPE2 (t))
>   	  result = NULL_TREE;
>   	else
> -	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
> +	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2,
> +				      tf_warning_or_error);
>         }
>         break;
>       case TYPE_PACK_EXPANSION:
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index 8b60cbbc167..53ac925a092 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -1632,7 +1632,7 @@ structural_comptypes (tree t1, tree t2, int strict)
>       case TRAIT_TYPE:
>         if (TRAIT_TYPE_KIND (t1) != TRAIT_TYPE_KIND (t2))
>   	return false;
> -      if (!same_type_p (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
> +      if (!cp_tree_equal (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
>   	  || !cp_tree_equal (TRAIT_TYPE_TYPE2 (t1), TRAIT_TYPE_TYPE2 (t2)))
>   	return false;
>         break;
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element1.C b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> new file mode 100644
> index 00000000000..46858555502
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> @@ -0,0 +1,19 @@
> +// { dg-do compile { target c++11 } }
> +
> +using ty0 = __type_pack_element<0, int>;
> +using ty0 = __type_pack_element<0, int, char>;
> +using ty0 = int;
> +
> +using ty1 = __type_pack_element<1, int, char>;
> +using ty1 = __type_pack_element<(6 - 5) * 1, int, char>;
> +using ty1 = char;
> +
> +template<int N, class... Ts>
> +using __const_type_pack_element_t = const __type_pack_element<N, Ts...>;
> +
> +using ty2 = __const_type_pack_element_t<2, int, char, long>;
> +using ty2 = const long;
> +
> +template<class T> struct A { };
> +using ty3 = __type_pack_element<3, int, int, int, A<int>>;
> +using ty3 = A<int>;
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element2.C b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> new file mode 100644
> index 00000000000..1bf77534097
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> @@ -0,0 +1,14 @@
> +// { dg-do compile { target c++11 } }
> +
> +int p;
> +
> +using type = __type_pack_element<&p, int>;      // { dg-error "not an integral constant" }
> +using type = __type_pack_element<1, int>;       // { dg-error "out of range" }
> +using type = __type_pack_element<2, int, char>; // { dg-error "out of range" }
> +using type = __type_pack_element<-1, int>;      // { dg-error "negative" }
> +
> +template<int N, class... Ts>
> +using __type_pack_element_t = __type_pack_element<N, Ts...>;
> +// { dg-error "out of range" "" { target *-*-* } .-1 }
> +
> +using type = __type_pack_element_t<3, int, char, long>; // { dg-message "here" }
> diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element3.C b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
> new file mode 100644
> index 00000000000..269f84f464f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
> @@ -0,0 +1,22 @@
> +// { dg-do compile { target c++11 } }
> +
> +template<class T, T N, class... Ts, class = __type_pack_element<N, Ts...>>
> +constexpr int f(int) { return 1; }
> +
> +template<class T, T N, class... Ts>
> +constexpr int f(...) { return 2; };
> +
> +int p;
> +
> +static_assert(f<int, 0, void, char>(0) == 1, "");
> +static_assert(f<int, 1, void, char>(0) == 1, "");
> +static_assert(f<int, 2, void, char>(0) == 2, "");
> +static_assert(f<int*, &p, void, char>(0) == 2, "");
> +
> +template<class T, class U> struct A;
> +template<class T> struct A<T, __type_pack_element<sizeof(T), void, long>> { };
> +template struct A<char, long>;
> +
> +template<class T, class U> struct B;
> +template<class T> struct B<T, __type_pack_element<0, T, short>> { };
> +template struct B<int, int>;
> diff --git a/libstdc++-v3/include/bits/utility.h b/libstdc++-v3/include/bits/utility.h
> index abaaae2dd33..4692aa0c9b0 100644
> --- a/libstdc++-v3/include/bits/utility.h
> +++ b/libstdc++-v3/include/bits/utility.h
> @@ -224,6 +224,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>   #endif // C++17
>   #endif // C++14
>   
> +#if __has_builtin(__type_pack_element)
> +  template<size_t _Np, typename... _Types>
> +    struct _Nth_type
> +    { using type = __type_pack_element<_Np, _Types...>; };
> +#else
>     template<size_t _Np, typename... _Types>
>       struct _Nth_type
>       { };
> @@ -262,6 +267,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       struct _Nth_type<1, _Tp0, _Tp1, _Tp2, _Rest...>
>       { using type = _Tp1; };
>   #endif
> +#endif
>   
>   #if __cplusplus > 202002L
>   #define __cpp_lib_ranges_zip 202110L // for <tuple> and <utility>
> diff --git a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> index 9bce0b1caa2..dd0df9be1ea 100644
> --- a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> +++ b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> @@ -61,3 +61,4 @@ test03()
>   
>   // { dg-error "tuple index must be in range" "" { target *-*-* } 0 }
>   // { dg-prune-output "no type named 'type' in .*_Nth_type" }
> +// { dg-prune-output "'__type_pack_element' index is out of range" }


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
  2023-04-18 19:09               ` Jason Merrill
@ 2023-04-18 19:23                 ` Patrick Palka
  0 siblings, 0 replies; 14+ messages in thread
From: Patrick Palka @ 2023-04-18 19:23 UTC (permalink / raw)
  To: Jason Merrill
  Cc: Patrick Palka, Jonathan Wakely, libstdc++, gcc-patches, Marek Polacek

On Tue, 18 Apr 2023, Jason Merrill wrote:

> On 4/11/23 10:21, Patrick Palka wrote:
> > On Thu, 26 Jan 2023, Jason Merrill wrote:
> > 
> > > On 1/25/23 15:35, Patrick Palka wrote:
> > > > On Tue, 17 Jan 2023, Jason Merrill wrote:
> > > > 
> > > > > On 1/9/23 14:25, Patrick Palka via Gcc-patches wrote:
> > > > > > On Mon, 9 Jan 2023, Patrick Palka wrote:
> > > > > > 
> > > > > > > On Wed, 5 Oct 2022, Patrick Palka wrote:
> > > > > > > 
> > > > > > > > On Thu, 7 Jul 2022, Jonathan Wakely via Gcc-patches wrote:
> > > > > > > > 
> > > > > > > > > This adds a new built-in to replace the recursive class
> > > > > > > > > template
> > > > > > > > > instantiations done by traits such as std::tuple_element and
> > > > > > > > > std::variant_alternative. The purpose is to select the Nth
> > > > > > > > > type
> > > > > > > > > from a
> > > > > > > > > list of types, e.g. __builtin_type_pack_element(1, char, int,
> > > > > > > > > float)
> > > > > > > > > is
> > > > > > > > > int.
> > > > > > > > > 
> > > > > > > > > For a pathological example tuple_element_t<1000, tuple<2000
> > > > > > > > > types...>>
> > > > > > > > > the compilation time is reduced by more than 90% and the
> > > > > > > > > memory
> > > > > > > > > used
> > > > > > > > > by
> > > > > > > > > the compiler is reduced by 97%. In realistic examples the
> > > > > > > > > gains
> > > > > > > > > will
> > > > > > > > > be
> > > > > > > > > much smaller, but still relevant.
> > > > > > > > > 
> > > > > > > > > Clang has a similar built-in, __type_pack_element<N, T...>,
> > > > > > > > > but
> > > > > > > > > that's
> > > > > > > > > a
> > > > > > > > > "magic template" built-in using <> syntax, which GCC doesn't
> > > > > > > > > support.
> > > > > > > > > So
> > > > > > > > > this provides an equivalent feature, but as a built-in
> > > > > > > > > function
> > > > > > > > > using
> > > > > > > > > parens instead of <>. I don't really like the name "type pack
> > > > > > > > > element"
> > > > > > > > > (it gives you an element from a pack of types) but the
> > > > > > > > > semi-consistency
> > > > > > > > > with Clang seems like a reasonable argument in favour of
> > > > > > > > > keeping
> > > > > > > > > the
> > > > > > > > > name. I'd be open to alternative names though, e.g.
> > > > > > > > > __builtin_nth_type
> > > > > > > > > or __builtin_type_at_index.
> > > > > > > > 
> > > > > > > > Rather than giving the trait a different name from
> > > > > > > > __type_pack_element,
> > > > > > > > I wonder if we could just special case cp_parser_trait to expect
> > > > > > > > <>
> > > > > > > > instead of parens for this trait?
> > > > > > > > 
> > > > > > > > Btw the frontend recently got a generic TRAIT_TYPE tree code,
> > > > > > > > which
> > > > > > > > gets
> > > > > > > > rid of much of the boilerplate of adding a new type-yielding
> > > > > > > > built-in
> > > > > > > > trait, see e.g. cp-trait.def.
> > > > > > > 
> > > > > > > Here's a tested patch based on Jonathan's original patch that
> > > > > > > implements
> > > > > > > the built-in in terms of TRAIT_TYPE, names it __type_pack_element
> > > > > > > instead of __builtin_type_pack_element, and treats invocations of
> > > > > > > it
> > > > > > > like a template-id instead of a call (to match Clang).
> > > > > > > 
> > > > > > > -- >8 --
> > > > > > > 
> > > > > > > Subject: [PATCH] c++: Define built-in for std::tuple_element
> > > > > > > [PR100157]
> > > > > > > 
> > > > > > > This adds a new built-in to replace the recursive class template
> > > > > > > instantiations done by traits such as std::tuple_element and
> > > > > > > std::variant_alternative.  The purpose is to select the Nth type
> > > > > > > from
> > > > > > > a
> > > > > > > list of types, e.g. __type_pack_element<1, char, int, float> is
> > > > > > > int.
> > > > > > > We implement it as a special kind of TRAIT_TYPE.
> > > > > > > 
> > > > > > > For a pathological example tuple_element_t<1000, tuple<2000
> > > > > > > types...>>
> > > > > > > the compilation time is reduced by more than 90% and the memory
> > > > > > > used
> > > > > > > by
> > > > > > > the compiler is reduced by 97%.  In realistic examples the gains
> > > > > > > will
> > > > > > > be
> > > > > > > much smaller, but still relevant.
> > > > > > > 
> > > > > > > Unlike the other built-in traits, __type_pack_element uses
> > > > > > > template-id
> > > > > > > syntax instead of call syntax and is SFINAE-enabled, matching
> > > > > > > Clang's
> > > > > > > implementation.  And like the other built-in traits, it's not
> > > > > > > mangleable
> > > > > > > so we can't use it directly in function signatures.
> > > > > > > 
> > > > > > > Some caveats:
> > > > > > > 
> > > > > > >      * Clang's version of the built-in seems to act like a "magic
> > > > > > > template"
> > > > > > >        that can e.g. be used as a template template argument.  For
> > > > > > > simplicity
> > > > > > >        we implement it in a more ad-hoc way.
> > > > > > >      * Our parsing of the <>'s in __type_pack_element<...> is
> > > > > > > currently
> > > > > > >        rudimentary and doesn't try to disambiguate a trailing >>
> > > > > > > vs > >
> > > > > > >        as cp_parser_enclosed_template_argument_list does.
> > > > > > 
> > > > > > Hmm, this latter caveat turns out to be inconvenient (for code such
> > > > > > as
> > > > > > type_pack_element3.C) and admits an easy workaround inspired by what
> > > > > > cp_parser_enclosed_template_argument_list does.
> > > > > > 
> > > > > > v2: Consider the >> in __type_pack_element<0, int, char>> to be two
> > > > > > >'s.
> > > > > >        Handle non-type TRAIT_TYPE_TYPE1 in strip_typedefs (for sake
> > > > > > of
> > > > > >        CPTK_TYPE_PACK_ELEMENT).
> > > > > 
> > > > > Why not use cp_parser_enclosed_template_argument_list directly?
> > > > 
> > > > If we used cp_parser_enclosed_template_argument_list we would then need
> > > > to convert the returned TREE_VEC into a TREE_LIST and also diagnose
> > > > argument kind mismatches (i.e. verify the first argument is an
> > > > expression and the rest are types).  It seemed like more complexity
> > > > overall then just duplicating the >> splitting logic, but I can do that
> > > > if you prefer?
> > > 
> > > I think I would prefer that, parser stuff can be pretty subtle.
> > > 
> > > Instead of turning the TREE_VEC into a TREE_LIST, we could handle TREE_VEC
> > > as
> > > a trait operand?
> > 
> > Sorry for the late follow up...  Here's an updated patch that uses
> > cp_parser_enclosed_template_argument_list instead of copying the parsing
> > logic from there.  I put off handling TREE_VEC as a trait operand for
> > now.  We could convert all variadic traits to use TREE_VEC instead of
> > TREE_LIST at once in a followup patch.
> > 
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, also tested against
> > libc++'s tuple/variant impl for good measure (which uses
> > __type_pack_element when available).
> > 
> > -- >8 --
> > 
> > Subject: [PATCH] c++: Define built-in for std::tuple_element [PR100157]
> > 
> > This adds a new built-in to replace the recursive class template
> > instantiations done by traits such as std::tuple_element and
> > std::variant_alternative.  The purpose is to select the Nth type from a
> > list of types, e.g. __type_pack_element<1, char, int, float> is int.
> > We implement it as a special kind of TRAIT_TYPE.
> > 
> > For a pathological example tuple_element_t<1000, tuple<2000 types...>>
> > the compilation time is reduced by more than 90% and the memory  used by
> > the compiler is reduced by 97%.  In realistic examples the gains will be
> > much smaller, but still relevant.
> > 
> > Unlike the other built-in traits, __type_pack_element uses template-id
> > syntax instead of call syntax and is SFINAE-enabled, matching Clang's
> > implementation.  And like the other built-in traits, it's not mangleable
> > so we can't use it directly in function signatures.
> > 
> > N.B. Clang seems to implement __type_pack_element as a first-class
> > template that can e.g. be used as a template-template argument.  For
> > simplicity we implement it in a more ad-hoc way.
> > 
> > Co-authored-by: Jonathan Wakely <jwakely@redhat.com>
> > 
> > 	PR c++/100157
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* cp-trait.def (TYPE_PACK_ELEMENT): Define.
> > 	* cp-tree.h (finish_trait_type): Add complain parameter.
> > 	* cxx-pretty-print.cc (pp_cxx_trait): Handle
> > 	CPTK_TYPE_PACK_ELEMENT.
> > 	* parser.cc (cp_parser_constant_expression): Document default
> > 	arguments.
> > 	(cp_parser_trait): Handle CPTK_TYPE_PACK_ELEMENT.  Pass
> > 	tf_warning_or_error to finish_trait_type.
> > 	* pt.cc (tsubst) <case TRAIT_TYPE>: Handle non-type first
> > 	argument.  Pass complain to finish_trait_type.
> > 	* semantics.cc (finish_type_pack_element): Define.
> > 	(finish_trait_type): Add complain parameter.  Handle
> > 	CPTK_TYPE_PACK_ELEMENT.
> > 	* tree.cc (strip_typedefs): Handle non-type first argument.
> > 	Pass tf_warning_or_error to finish_trait_type.
> > 	* typeck.cc (structural_comptypes) <case TRAIT_TYPE>: Use
> > 	cp_tree_equal instead of same_type_p for the first argument.
> > 
> > libstdc++-v3/ChangeLog:
> > 
> > 	* include/bits/utility.h (_Nth_type): Conditionally define in
> > 	terms of __type_pack_element if available.
> > 	* testsuite/20_util/tuple/element_access/get_neg.cc: Prune
> > 	additional errors from the new built-in.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/ext/type_pack_element1.C: New test.
> > 	* g++.dg/ext/type_pack_element2.C: New test.
> > 	* g++.dg/ext/type_pack_element3.C: New test.
> > ---
> >   gcc/cp/cp-trait.def                           |  1 +
> >   gcc/cp/cp-tree.h                              |  2 +-
> >   gcc/cp/cxx-pretty-print.cc                    | 21 ++++++---
> >   gcc/cp/parser.cc                              | 45 ++++++++++++++++---
> >   gcc/cp/pt.cc                                  |  8 +++-
> >   gcc/cp/semantics.cc                           | 39 +++++++++++++++-
> >   gcc/cp/tree.cc                                | 10 +++--
> >   gcc/cp/typeck.cc                              |  2 +-
> >   gcc/testsuite/g++.dg/ext/type_pack_element1.C | 19 ++++++++
> >   gcc/testsuite/g++.dg/ext/type_pack_element2.C | 14 ++++++
> >   gcc/testsuite/g++.dg/ext/type_pack_element3.C | 22 +++++++++
> >   libstdc++-v3/include/bits/utility.h           |  6 +++
> >   .../20_util/tuple/element_access/get_neg.cc   |  1 +
> >   13 files changed, 170 insertions(+), 20 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element1.C
> >   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element2.C
> >   create mode 100644 gcc/testsuite/g++.dg/ext/type_pack_element3.C
> > 
> > diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
> > index bac593c0094..8b7fece0cc8 100644
> > --- a/gcc/cp/cp-trait.def
> > +++ b/gcc/cp/cp-trait.def
> > @@ -91,6 +91,7 @@ DEFTRAIT_TYPE (REMOVE_CV, "__remove_cv", 1)
> >   DEFTRAIT_TYPE (REMOVE_REFERENCE, "__remove_reference", 1)
> >   DEFTRAIT_TYPE (REMOVE_CVREF, "__remove_cvref", 1)
> >   DEFTRAIT_TYPE (UNDERLYING_TYPE,  "__underlying_type", 1)
> > +DEFTRAIT_TYPE (TYPE_PACK_ELEMENT, "__type_pack_element", -1)
> >     /* These traits yield a type pack, not a type, and are represented by
> >      cp_parser_trait as a special BASES tree instead of a TRAIT_TYPE tree.
> > */
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index 622752ae4e6..1c897c2ce8f 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -7761,7 +7761,7 @@ extern tree finish_decltype_type                (tree,
> > bool, tsubst_flags_t);
> >   extern tree fold_builtin_is_corresponding_member (location_t, int, tree
> > *);
> >   extern tree fold_builtin_is_pointer_inverconvertible_with_class
> > (location_t, int, tree *);
> >   extern tree finish_trait_expr			(location_t, enum
> > cp_trait_kind, tree, tree);
> > -extern tree finish_trait_type			(enum cp_trait_kind,
> > tree, tree);
> > +extern tree finish_trait_type			(enum cp_trait_kind,
> > tree, tree, tsubst_flags_t);
> >   extern tree build_lambda_expr                   (void);
> >   extern tree build_lambda_object			(tree);
> >   extern tree begin_lambda_type                   (tree);
> > diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
> > index 7f4556d0da2..c33919873f1 100644
> > --- a/gcc/cp/cxx-pretty-print.cc
> > +++ b/gcc/cp/cxx-pretty-print.cc
> > @@ -2625,11 +2625,19 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
> >   #undef DEFTRAIT
> >       }
> >   -  pp_cxx_left_paren (pp);
> > -  if (TYPE_P (type1))
> > -    pp->type_id (type1);
> > +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> > +    {
> > +      pp_cxx_begin_template_argument_list (pp);
> > +      pp->expression (type1);
> > +    }
> >     else
> > -    pp->expression (type1);
> > +    {
> > +      pp_cxx_left_paren (pp);
> > +      if (TYPE_P (type1))
> > +	pp->type_id (type1);
> > +      else
> > +	pp->expression (type1);
> > +    }
> >     if (type2)
> >       {
> >         if (TREE_CODE (type2) != TREE_LIST)
> > @@ -2644,7 +2652,10 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
> >   	    pp->type_id (TREE_VALUE (arg));
> >   	  }
> >       }
> > -  pp_cxx_right_paren (pp);
> > +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> > +    pp_cxx_end_template_argument_list (pp);
> > +  else
> > +    pp_cxx_right_paren (pp);
> >   }
> >     // requires-clause:
> > diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> > index a6341b98af2..281292b462d 100644
> > --- a/gcc/cp/parser.cc
> > +++ b/gcc/cp/parser.cc
> > @@ -10730,9 +10730,9 @@ cp_parser_expression (cp_parser* parser, cp_id_kind
> > * pidk,
> >     static cp_expr
> >   cp_parser_constant_expression (cp_parser* parser,
> > -			       int allow_non_constant_p,
> > -			       bool *non_constant_p,
> > -			       bool strict_p)
> > +			       int allow_non_constant_p /* = 0 */,
> > +			       bool *non_constant_p /* = NULL */,
> > +			       bool strict_p /* = false */)
> >   {
> >     bool saved_integral_constant_expression_p;
> >     bool saved_allow_non_integral_constant_expression_p;
> > @@ -10959,10 +10959,10 @@ cp_parser_trait (cp_parser* parser, enum rid
> > keyword)
> >     cp_lexer_consume_token (parser->lexer);
> >       matching_parens parens;
> > -  parens.require_open (parser);
> >       if (kind == CPTK_IS_DEDUCIBLE)
> >       {
> > +      parens.require_open (parser);
> >         const cp_token* token = cp_lexer_peek_token (parser->lexer);
> >         type1 = cp_parser_id_expression (parser,
> >   				       /*template_keyword_p=*/false,
> > @@ -10972,8 +10972,18 @@ cp_parser_trait (cp_parser* parser, enum rid
> > keyword)
> >   				       /*optional_p=*/false);
> >         type1 = cp_parser_lookup_name_simple (parser, type1,
> > token->location);
> >       }
> > +  else if (kind == CPTK_TYPE_PACK_ELEMENT)
> > +    {
> > +      /* __type_pack_element takes an expression as its first argument and
> > uses
> > +	 template-id syntax instead of function call syntax (for consistency
> > +	 with Clang).  We special case these properties of __type_pack_element
> > +	 here and elsewhere.  */
> > +      cp_parser_require (parser, CPP_LESS, RT_LESS);
> > +      type1 = cp_parser_constant_expression (parser);
> > +    }
> >     else
> >       {
> > +      parens.require_open (parser);
> >         type_id_in_expr_sentinel s (parser);
> >         type1 = cp_parser_type_id (parser);
> >       }
> > @@ -10981,7 +10991,24 @@ cp_parser_trait (cp_parser* parser, enum rid
> > keyword)
> >     if (type1 == error_mark_node)
> >       return error_mark_node;
> >   -  if (binary)
> > +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> > +    {
> > +      cp_parser_require (parser, CPP_COMMA, RT_COMMA);
> > +      tree rest = cp_parser_enclosed_template_argument_list (parser);
> > +      for (tree elt : tree_vec_range (rest))
> > +	{
> > +	  if (!TYPE_P (elt))
> > +	    {
> > +	      error_at (cp_expr_loc_or_input_loc (elt),
> > +			"trailing argument to %<__type_pack_element%> "
> > +			"is not a type");
> > +	      return error_mark_node;
> > +	    }
> > +	  type2 = tree_cons (NULL_TREE, elt, type2);
> > +	}
> > +      type2 = nreverse (type2);
> > +    }
> > +  else if (binary)
> >       {
> >         cp_parser_require (parser, CPP_COMMA, RT_COMMA);
> >   @@ -11012,7 +11039,11 @@ cp_parser_trait (cp_parser* parser, enum rid
> > keyword)
> >       }
> >       location_t finish_loc = cp_lexer_peek_token (parser->lexer)->location;
> > -  parens.require_close (parser);
> > +  if (kind == CPTK_TYPE_PACK_ELEMENT)
> > +    /* cp_parser_enclosed_template_argument_list above already took care
> > +       of parsing closing '>'.  */;
> > +  else
> > +    parens.require_close (parser);
> >       /* Construct a location of the form:
> >          __is_trivially_copyable(_Tp)
> > @@ -11030,7 +11061,7 @@ cp_parser_trait (cp_parser* parser, enum rid
> > keyword)
> >         return cp_expr (finish_bases (type1, true), trait_loc);
> >       default:
> >         if (type)
> > -	return finish_trait_type (kind, type1, type2);
> > +	return finish_trait_type (kind, type1, type2, tf_warning_or_error);
> >         else
> >   	return finish_trait_expr (trait_loc, kind, type1, type2);
> >       }
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 185e23f642d..dec410dbaba 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -16695,9 +16695,13 @@ tsubst (tree t, tree args, tsubst_flags_t complain,
> > tree in_decl)
> >         case TRAIT_TYPE:
> >         {
> > -	tree type1 = tsubst (TRAIT_TYPE_TYPE1 (t), args, complain, in_decl);
> > +	tree type1 = TRAIT_TYPE_TYPE1 (t);
> > +	if (TYPE_P (type1))
> > +	  type1 = tsubst (type1, args, complain, in_decl);
> > +	else
> > +	  type1 = tsubst_copy_and_build (type1, args, complain, in_decl);
> >   	tree type2 = tsubst (TRAIT_TYPE_TYPE2 (t), args, complain, in_decl);
> 
> In the case of __builtin_type_pack_element where the original is something
> like T..., we could optimize even further and select the element without
> actually doing the expansion?

Ah, IIUC that'd happen automatically if TRAIT_TYPE_TYPE2 were a TREE_VEC
instead of TREE_LIST, thanks to the tsubst_template_args optimization:

pt.c#L13803:

  /* If T consists of only a pack expansion for which substitution yielded
     a TREE_VEC of the expanded elements, then reuse that TREE_VEC instead
     of effectively making a copy.  */
  if (len == 1
      && PACK_EXPANSION_P (TREE_VEC_ELT (t, 0))
      && TREE_CODE (elts[0]) == TREE_VEC)
    return elts[0];

All the more reason to use TREE_VEC for TRAIT_TYPE_TYPE2!

> 
> The patch is OK for trunk as is.

Thanks.

> 
> Jason
> 
> > -	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
> > +	type = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2,
> > complain);
> >   	return cp_build_qualified_type (type,
> >   					cp_type_quals (t) | cp_type_quals
> > (type),
> >   					complain | tf_ignore_bad_quals);
> > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> > index 99a76e3ed65..74b852c500a 100644
> > --- a/gcc/cp/semantics.cc
> > +++ b/gcc/cp/semantics.cc
> > @@ -4470,6 +4470,36 @@ finish_underlying_type (tree type)
> >     return underlying_type;
> >   }
> >   +/* 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)
> > +{
> > +  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");
> > +      return error_mark_node;
> > +    }
> > +  HOST_WIDE_INT val = tree_to_shwi (idx);
> > +  if (val < 0)
> > +    {
> > +      if (complain & tf_error)
> > +	error ("%<__type_pack_element%> index is negative");
> > +      return error_mark_node;
> > +    }
> > +  tree result = chain_index (val, types);
> > +  if (!result)
> > +    {
> > +      if (complain & tf_error)
> > +	error ("%<__type_pack_element%> index is out of range");
> > +      return error_mark_node;
> > +    }
> > +  return TREE_VALUE (result);
> > +}
> > +
> >   /* Implement the __direct_bases keyword: Return the direct base classes
> >      of type.  */
> >   @@ -12240,7 +12270,8 @@ finish_trait_expr (location_t loc, cp_trait_kind
> > kind, tree type1, tree type2)
> >   /* Process a trait type.  */
> >     tree
> > -finish_trait_type (cp_trait_kind kind, tree type1, tree type2)
> > +finish_trait_type (cp_trait_kind kind, tree type1, tree type2,
> > +		   tsubst_flags_t complain)
> >   {
> >     if (type1 == error_mark_node
> >         || type2 == error_mark_node)
> > @@ -12264,17 +12295,23 @@ finish_trait_type (cp_trait_kind kind, tree type1,
> > tree type2)
> >       {
> >       case CPTK_UNDERLYING_TYPE:
> >         return finish_underlying_type (type1);
> > +
> >       case CPTK_REMOVE_CV:
> >         return cv_unqualified (type1);
> > +
> >       case CPTK_REMOVE_REFERENCE:
> >         if (TYPE_REF_P (type1))
> >   	type1 = TREE_TYPE (type1);
> >         return type1;
> > +
> >       case CPTK_REMOVE_CVREF:
> >         if (TYPE_REF_P (type1))
> >   	type1 = TREE_TYPE (type1);
> >         return cv_unqualified (type1);
> >   +    case CPTK_TYPE_PACK_ELEMENT:
> > +      return finish_type_pack_element (type1, type2, complain);
> > +
> >   #define DEFTRAIT_EXPR(CODE, NAME, ARITY) \
> >       case CPTK_##CODE:
> >   #include "cp-trait.def"
> > diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> > index 16b8fcb7d57..2c22fac17ee 100644
> > --- a/gcc/cp/tree.cc
> > +++ b/gcc/cp/tree.cc
> > @@ -1792,14 +1792,18 @@ strip_typedefs (tree t, bool *remove_attributes /* =
> > NULL */,
> >         break;
> >       case TRAIT_TYPE:
> >         {
> > -	tree type1 = strip_typedefs (TRAIT_TYPE_TYPE1 (t),
> > -				     remove_attributes, flags);
> > +	tree type1 = TRAIT_TYPE_TYPE1 (t);
> > +	if (TYPE_P (type1))
> > +	  type1 = strip_typedefs (type1, remove_attributes, flags);
> > +	else
> > +	  type1 = strip_typedefs_expr (type1, remove_attributes, flags);
> >   	tree type2 = strip_typedefs (TRAIT_TYPE_TYPE2 (t),
> >   				     remove_attributes, flags);
> >   	if (type1 == TRAIT_TYPE_TYPE1 (t) && type2 == TRAIT_TYPE_TYPE2 (t))
> >   	  result = NULL_TREE;
> >   	else
> > -	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2);
> > +	  result = finish_trait_type (TRAIT_TYPE_KIND (t), type1, type2,
> > +				      tf_warning_or_error);
> >         }
> >         break;
> >       case TYPE_PACK_EXPANSION:
> > diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> > index 8b60cbbc167..53ac925a092 100644
> > --- a/gcc/cp/typeck.cc
> > +++ b/gcc/cp/typeck.cc
> > @@ -1632,7 +1632,7 @@ structural_comptypes (tree t1, tree t2, int strict)
> >       case TRAIT_TYPE:
> >         if (TRAIT_TYPE_KIND (t1) != TRAIT_TYPE_KIND (t2))
> >   	return false;
> > -      if (!same_type_p (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
> > +      if (!cp_tree_equal (TRAIT_TYPE_TYPE1 (t1), TRAIT_TYPE_TYPE1 (t2))
> >   	  || !cp_tree_equal (TRAIT_TYPE_TYPE2 (t1), TRAIT_TYPE_TYPE2 (t2)))
> >   	return false;
> >         break;
> > diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> > b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> > new file mode 100644
> > index 00000000000..46858555502
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/ext/type_pack_element1.C
> > @@ -0,0 +1,19 @@
> > +// { dg-do compile { target c++11 } }
> > +
> > +using ty0 = __type_pack_element<0, int>;
> > +using ty0 = __type_pack_element<0, int, char>;
> > +using ty0 = int;
> > +
> > +using ty1 = __type_pack_element<1, int, char>;
> > +using ty1 = __type_pack_element<(6 - 5) * 1, int, char>;
> > +using ty1 = char;
> > +
> > +template<int N, class... Ts>
> > +using __const_type_pack_element_t = const __type_pack_element<N, Ts...>;
> > +
> > +using ty2 = __const_type_pack_element_t<2, int, char, long>;
> > +using ty2 = const long;
> > +
> > +template<class T> struct A { };
> > +using ty3 = __type_pack_element<3, int, int, int, A<int>>;
> > +using ty3 = A<int>;
> > diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> > b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> > new file mode 100644
> > index 00000000000..1bf77534097
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/ext/type_pack_element2.C
> > @@ -0,0 +1,14 @@
> > +// { dg-do compile { target c++11 } }
> > +
> > +int p;
> > +
> > +using type = __type_pack_element<&p, int>;      // { dg-error "not an
> > integral constant" }
> > +using type = __type_pack_element<1, int>;       // { dg-error "out of
> > range" }
> > +using type = __type_pack_element<2, int, char>; // { dg-error "out of
> > range" }
> > +using type = __type_pack_element<-1, int>;      // { dg-error "negative" }
> > +
> > +template<int N, class... Ts>
> > +using __type_pack_element_t = __type_pack_element<N, Ts...>;
> > +// { dg-error "out of range" "" { target *-*-* } .-1 }
> > +
> > +using type = __type_pack_element_t<3, int, char, long>; // { dg-message
> > "here" }
> > diff --git a/gcc/testsuite/g++.dg/ext/type_pack_element3.C
> > b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
> > new file mode 100644
> > index 00000000000..269f84f464f
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/ext/type_pack_element3.C
> > @@ -0,0 +1,22 @@
> > +// { dg-do compile { target c++11 } }
> > +
> > +template<class T, T N, class... Ts, class = __type_pack_element<N, Ts...>>
> > +constexpr int f(int) { return 1; }
> > +
> > +template<class T, T N, class... Ts>
> > +constexpr int f(...) { return 2; };
> > +
> > +int p;
> > +
> > +static_assert(f<int, 0, void, char>(0) == 1, "");
> > +static_assert(f<int, 1, void, char>(0) == 1, "");
> > +static_assert(f<int, 2, void, char>(0) == 2, "");
> > +static_assert(f<int*, &p, void, char>(0) == 2, "");
> > +
> > +template<class T, class U> struct A;
> > +template<class T> struct A<T, __type_pack_element<sizeof(T), void, long>> {
> > };
> > +template struct A<char, long>;
> > +
> > +template<class T, class U> struct B;
> > +template<class T> struct B<T, __type_pack_element<0, T, short>> { };
> > +template struct B<int, int>;
> > diff --git a/libstdc++-v3/include/bits/utility.h
> > b/libstdc++-v3/include/bits/utility.h
> > index abaaae2dd33..4692aa0c9b0 100644
> > --- a/libstdc++-v3/include/bits/utility.h
> > +++ b/libstdc++-v3/include/bits/utility.h
> > @@ -224,6 +224,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >   #endif // C++17
> >   #endif // C++14
> >   +#if __has_builtin(__type_pack_element)
> > +  template<size_t _Np, typename... _Types>
> > +    struct _Nth_type
> > +    { using type = __type_pack_element<_Np, _Types...>; };
> > +#else
> >     template<size_t _Np, typename... _Types>
> >       struct _Nth_type
> >       { };
> > @@ -262,6 +267,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >       struct _Nth_type<1, _Tp0, _Tp1, _Tp2, _Rest...>
> >       { using type = _Tp1; };
> >   #endif
> > +#endif
> >     #if __cplusplus > 202002L
> >   #define __cpp_lib_ranges_zip 202110L // for <tuple> and <utility>
> > diff --git a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> > b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> > index 9bce0b1caa2..dd0df9be1ea 100644
> > --- a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> > +++ b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
> > @@ -61,3 +61,4 @@ test03()
> >     // { dg-error "tuple index must be in range" "" { target *-*-* } 0 }
> >   // { dg-prune-output "no type named 'type' in .*_Nth_type" }
> > +// { dg-prune-output "'__type_pack_element' index is out of range" }
> 
> 


^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2023-04-18 19:23 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-07-07 17:14 [PATCH] c++: Define built-in for std::tuple_element [PR100157] Jonathan Wakely
2022-07-07 17:25 ` Marek Polacek
2022-07-07 19:28 ` Jason Merrill
2022-07-07 20:46   ` Jonathan Wakely
2022-10-05 13:43 ` Patrick Palka
2023-01-09 15:53   ` Patrick Palka
2023-01-09 19:25     ` Patrick Palka
2023-01-10 10:17       ` Jonathan Wakely
2023-01-17 18:04       ` Jason Merrill
2023-01-25 20:35         ` Patrick Palka
2023-01-26 17:47           ` Jason Merrill
2023-04-11 14:21             ` Patrick Palka
2023-04-18 19:09               ` Jason Merrill
2023-04-18 19:23                 ` Patrick Palka

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).