[PATCH] c++: Implement P0466R5 __cpp_lib_is_layout_compatible compiler helpers [PR101539]

Jason Merrill jason@redhat.com
Thu Aug 12 14:33:20 GMT 2021


On 8/3/21 3:05 AM, Jakub Jelinek wrote:
> Hi!
> 
> The following patch implements __is_layout_compatible trait and
> __builtin_is_corresponding_member helper function for the
> std::is_corresponding_member template function.
> For now it implements the IMHO buggy but
> standard definition of layout-compatible and std::is_layout_compatible
> requirements (that Jonathan was discussing to change),
> including ignoring of alignment differences, mishandling of bitfields in unions
> and [[no_unique_address]] issues with empty classes.
> Until we know what exactly is decided in a CWG that seems better to trying
> to guess what the standard will say, but of course if you have different
> ideas, the patch can change.

I think it's clear that if corresponding fields have different offsets 
or sizes, their containing types can't plausibly be layout-compatible. 
And if two types have different sizes or alignments, they can't be 
layout-compatible.

That leaves open the question of whether the presence or absence of 
no-op alignment specifiers makes a difference; Richard Smith's proposal 
would make that incompatible, I lean the other way, but don't feel 
strongly about it.

> For __builtin_is_corresponding_member, it will sorry if corresponding members
> could have different offsets (doesn't do so during constant evaluation but
> unless one uses the builtin directly, even using std::is_corresponding_member
> in constant expressions only will result in instantiation of the template and
> the code in the template doesn't have constant arguments and so can emit sorry).

As above, this case should be false without a sorry.  If they're at 
different offsets, they can't reasonably be part of the common initial 
sequence.

> For anonymous structs (GCC extension) it will recurse into the anonymous
> structs.  For anonymous unions it will emit another sorry if it can't prove such
> member types can't appear in the anonymous unions or anonymous aggregates in
> that union, because corresponding member is defined only using common initial
> sequence which is only defined for std-layout non-union class types and so I
> have no idea what to do otherwise in that case.

That makes sense for now.

> Bootstrapped/regtested on x86_64-linux and i686-linux.
> 
> 2021-08-03  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/101539
> gcc/c-family/
> 	* c-common.h (enum rid): Add RID_IS_LAYOUT_COMPATIBLE.
> 	* c-common.c (c_common_reswords): Add __is_layout_compatible.
> gcc/cp/
> 	* cp-tree.h (enum cp_trait_kind): Add CPTK_IS_LAYOUT_COMPATIBLE.
> 	(enum cp_built_in_function): Add CP_BUILT_IN_IS_CORRESPONDING_MEMBER.
> 	(fold_builtin_is_corresponding_member, layout_compatible_type_p):
> 	Declare.
> 	* parser.c (cp_parser_primary_expression): Handle
> 	RID_IS_LAYOUT_COMPATIBLE.
> 	(cp_parser_trait_expr): Likewise.
> 	* cp-objcp-common.c (names_builtin_p): Likewise.
> 	* constraint.cc (diagnose_trait_expr): Handle
> 	CPTK_IS_LAYOUT_COMPATIBLE.
> 	* decl.c (cxx_init_decl_processing): Register
> 	__builtin_is_corresponding_member builtin.
> 	* constexpr.c (cxx_eval_builtin_function_call): Handle
> 	CP_BUILT_IN_IS_CORRESPONDING_MEMBER builtin.
> 	* semantics.c (is_corresponding_member_union,
> 	is_corresponding_member_aggr, fold_builtin_is_corresponding_member):
> 	New functions.
> 	(trait_expr_value): Handle CPTK_IS_LAYOUT_COMPATIBLE.
> 	(finish_trait_expr): Likewise.
> 	* typeck.c (layout_compatible_type_p): New function.
> 	* cp-gimplify.c (cp_gimplify_expr): Fold
> 	CP_BUILT_IN_IS_CORRESPONDING_MEMBER.
> 	(cp_fold): Likewise.
> 	* tree.c (builtin_valid_in_constant_expr_p): Handle
> 	CP_BUILT_IN_IS_CORRESPONDING_MEMBER.
> 	* cxx-pretty-print.c (pp_cxx_trait_expression): Handle
> 	CPTK_IS_LAYOUT_COMPATIBLE.
> 	* class.c (remove_zero_width_bit_fields): Remove.
> 	(layout_class_type): Don't call it.
> gcc/testsuite/
> 	* g++.dg/cpp2a/is-corresponding-member1.C: New test.
> 	* g++.dg/cpp2a/is-corresponding-member2.C: New test.
> 	* g++.dg/cpp2a/is-corresponding-member3.C: New test.
> 	* g++.dg/cpp2a/is-corresponding-member4.C: New test.
> 	* g++.dg/cpp2a/is-corresponding-member5.C: New test.
> 	* g++.dg/cpp2a/is-corresponding-member6.C: New test.
> 	* g++.dg/cpp2a/is-corresponding-member7.C: New test.
> 	* g++.dg/cpp2a/is-corresponding-member8.C: New test.
> 	* g++.dg/cpp2a/is-layout-compatible1.C: New test.
> 	* g++.dg/cpp2a/is-layout-compatible2.C: New test.
> 	* g++.dg/cpp2a/is-layout-compatible3.C: New test.
> 
> --- gcc/c-family/c-common.h.jj	2021-07-31 18:35:23.879983218 +0200
> +++ gcc/c-family/c-common.h	2021-07-31 18:37:07.038600605 +0200
> @@ -173,7 +173,8 @@ enum rid
>     RID_IS_ABSTRACT,             RID_IS_AGGREGATE,
>     RID_IS_BASE_OF,              RID_IS_CLASS,
>     RID_IS_EMPTY,                RID_IS_ENUM,
> -  RID_IS_FINAL,                RID_IS_LITERAL_TYPE,
> +  RID_IS_FINAL,                RID_IS_LAYOUT_COMPATIBLE,
> +  RID_IS_LITERAL_TYPE,
>     RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF,
>     RID_IS_POD,                  RID_IS_POLYMORPHIC,
>     RID_IS_SAME_AS,
> --- gcc/c-family/c-common.c.jj	2021-07-31 09:17:09.190343988 +0200
> +++ gcc/c-family/c-common.c	2021-07-31 18:35:23.881983192 +0200
> @@ -420,6 +420,7 @@ const struct c_common_resword c_common_r
>     { "__is_empty",	RID_IS_EMPTY,	D_CXXONLY },
>     { "__is_enum",	RID_IS_ENUM,	D_CXXONLY },
>     { "__is_final",	RID_IS_FINAL,	D_CXXONLY },
> +  { "__is_layout_compatible", RID_IS_LAYOUT_COMPATIBLE, D_CXXONLY },
>     { "__is_literal_type", RID_IS_LITERAL_TYPE, D_CXXONLY },
>     { "__is_pointer_interconvertible_base_of",
>   			RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF, D_CXXONLY },
> --- gcc/cp/cp-tree.h.jj	2021-07-31 09:17:09.219343584 +0200
> +++ gcc/cp/cp-tree.h	2021-07-31 18:45:26.871901455 +0200
> @@ -1365,6 +1365,7 @@ enum cp_trait_kind
>     CPTK_IS_EMPTY,
>     CPTK_IS_ENUM,
>     CPTK_IS_FINAL,
> +  CPTK_IS_LAYOUT_COMPATIBLE,
>     CPTK_IS_LITERAL_TYPE,
>     CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF,
>     CPTK_IS_POD,
> @@ -6356,6 +6357,7 @@ struct GTY((chain_next ("%h.next"))) tin
>   enum cp_built_in_function {
>     CP_BUILT_IN_IS_CONSTANT_EVALUATED,
>     CP_BUILT_IN_INTEGER_PACK,
> +  CP_BUILT_IN_IS_CORRESPONDING_MEMBER,
>     CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS,
>     CP_BUILT_IN_SOURCE_LOCATION,
>     CP_BUILT_IN_LAST
> @@ -7572,6 +7574,7 @@ extern tree baselink_for_fns
>   extern void finish_static_assert                (tree, tree, location_t,
>   						 bool, bool);
>   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 build_lambda_expr                   (void);
> @@ -7798,6 +7801,7 @@ extern bool comp_except_specs			(const_t
>   extern bool comptypes				(tree, tree, int);
>   extern bool same_type_ignoring_top_level_qualifiers_p (tree, tree);
>   extern bool similar_type_p			(tree, tree);
> +extern bool layout_compatible_type_p		(tree, tree);
>   extern bool compparms				(const_tree, const_tree);
>   extern int comp_cv_qualification		(const_tree, const_tree);
>   extern int comp_cv_qualification		(int, int);
> --- gcc/cp/parser.c.jj	2021-07-31 09:35:18.801178476 +0200
> +++ gcc/cp/parser.c	2021-07-31 18:35:23.888983098 +0200
> @@ -5798,6 +5798,7 @@ cp_parser_primary_expression (cp_parser
>   	case RID_IS_EMPTY:
>   	case RID_IS_ENUM:
>   	case RID_IS_FINAL:
> +	case RID_IS_LAYOUT_COMPATIBLE:
>   	case RID_IS_LITERAL_TYPE:
>   	case RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
>   	case RID_IS_POD:
> @@ -10686,6 +10687,10 @@ cp_parser_trait_expr (cp_parser* parser,
>       case RID_IS_FINAL:
>         kind = CPTK_IS_FINAL;
>         break;
> +    case RID_IS_LAYOUT_COMPATIBLE:
> +      kind = CPTK_IS_LAYOUT_COMPATIBLE;
> +      binary = true;
> +      break;
>       case RID_IS_LITERAL_TYPE:
>         kind = CPTK_IS_LITERAL_TYPE;
>         break;
> --- gcc/cp/cp-objcp-common.c.jj	2021-07-31 09:17:09.217343612 +0200
> +++ gcc/cp/cp-objcp-common.c	2021-07-31 18:35:23.888983098 +0200
> @@ -413,6 +413,7 @@ names_builtin_p (const char *name)
>       case RID_IS_EMPTY:
>       case RID_IS_ENUM:
>       case RID_IS_FINAL:
> +    case RID_IS_LAYOUT_COMPATIBLE:
>       case RID_IS_LITERAL_TYPE:
>       case RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
>       case RID_IS_POD:
> --- gcc/cp/constraint.cc.jj	2021-07-31 09:17:09.217343612 +0200
> +++ gcc/cp/constraint.cc	2021-07-31 18:38:06.987797118 +0200
> @@ -3628,6 +3628,9 @@ diagnose_trait_expr (tree expr, tree arg
>       case CPTK_IS_FINAL:
>         inform (loc, "  %qT is not a final class", t1);
>         break;
> +    case CPTK_IS_LAYOUT_COMPATIBLE:
> +      inform (loc, "  %qT is not layout compatible with %qT", t1, t2);
> +      break;
>       case CPTK_IS_LITERAL_TYPE:
>         inform (loc, "  %qT is not a literal type", t1);
>         break;
> --- gcc/cp/decl.c.jj	2021-07-31 18:30:02.310287105 +0200
> +++ gcc/cp/decl.c	2021-07-31 18:47:06.450566828 +0200
> @@ -4470,6 +4470,13 @@ cxx_init_decl_processing (void)
>     tree bool_vaftype = build_varargs_function_type_list (boolean_type_node,
>   							NULL_TREE);
>     decl
> +    = add_builtin_function ("__builtin_is_corresponding_member",
> +			    bool_vaftype,
> +			    CP_BUILT_IN_IS_CORRESPONDING_MEMBER,
> +			    BUILT_IN_FRONTEND, NULL, NULL_TREE);
> +  set_call_expr_flags (decl, ECF_CONST | ECF_NOTHROW | ECF_LEAF);
> +
> +  decl
>       = add_builtin_function ("__builtin_is_pointer_interconvertible_with_class",
>   			    bool_vaftype,
>   			    CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS,
> --- gcc/cp/constexpr.c.jj	2021-07-31 09:17:09.217343612 +0200
> +++ gcc/cp/constexpr.c	2021-07-31 18:48:49.574184683 +0200
> @@ -1438,6 +1438,18 @@ cxx_eval_builtin_function_call (const co
>   	= fold_builtin_is_pointer_inverconvertible_with_class (loc, nargs,
>   							       args);
>       }
> +  else if (fndecl_built_in_p (fun,
> +			      CP_BUILT_IN_IS_CORRESPONDING_MEMBER,
> +			      BUILT_IN_FRONTEND))
> +    {
> +      location_t loc = EXPR_LOCATION (t);
> +      if (nargs >= 2)
> +	{
> +	  VERIFY_CONSTANT (args[0]);
> +	  VERIFY_CONSTANT (args[1]);
> +	}
> +      new_call = fold_builtin_is_corresponding_member (loc, nargs, args);
> +    }
>     else
>       new_call = fold_builtin_call_array (EXPR_LOCATION (t), TREE_TYPE (t),
>   					CALL_EXPR_FN (t), nargs, args);
> --- gcc/cp/semantics.c.jj	2021-07-31 09:17:09.229343445 +0200
> +++ gcc/cp/semantics.c	2021-08-02 15:42:04.735921585 +0200
> @@ -10670,6 +10670,308 @@ fold_builtin_is_pointer_inverconvertible
>   		      build_zero_cst (TREE_TYPE (arg)));
>   }
>   
> +/* Helper function for is_corresponding_member_aggr.  Return true if
> +   MEMBERTYPE pointer-to-data-member ARG can be found in anonymous
> +   union or structure BASETYPE.  */
> +
> +static bool
> +is_corresponding_member_union (tree basetype, tree membertype, tree arg)
> +{
> +  for (tree field = TYPE_FIELDS (basetype); field; field = DECL_CHAIN (field))
> +    if (TREE_CODE (field) != FIELD_DECL || DECL_BIT_FIELD_TYPE (field))
> +      continue;
> +    else if (same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (field),
> +							membertype))
> +      {
> +	if (TREE_CODE (arg) != INTEGER_CST
> +	    || tree_int_cst_equal (arg, byte_position (field)))
> +	  return true;
> +      }
> +    else if (ANON_AGGR_TYPE_P (TREE_TYPE (field)))
> +      {
> +	tree narg = arg;
> +	if (TREE_CODE (basetype) != UNION_TYPE
> +	    && TREE_CODE (narg) == INTEGER_CST)
> +	  narg = size_binop (MINUS_EXPR, arg, byte_position (field));
> +	if (is_corresponding_member_union (TREE_TYPE (field),
> +					   membertype, narg))
> +	  return true;
> +      }
> +  return false;
> +}
> +
> +/* Helper function for fold_builtin_is_corresponding_member call.
> +   Return boolean_false_node if MEMBERTYPE1 BASETYPE1::*ARG1 and
> +   MEMBERTYPE2 BASETYPE2::*ARG2 aren't corresponding members,
> +   boolean_true_node if they are corresponding members, or for
> +   non-constant ARG2 the highest member offset for corresponding
> +   members.  */
> +
> +static tree
> +is_corresponding_member_aggr (location_t loc, tree basetype1, tree membertype1,
> +			      tree arg1, tree basetype2, tree membertype2,
> +			      tree arg2)
> +{
> +  tree field1 = TYPE_FIELDS (basetype1);
> +  tree field2 = TYPE_FIELDS (basetype2);
> +  tree ret = boolean_false_node;
> +  while (1)
> +    {
> +      while (field1 && TREE_CODE (field1) != FIELD_DECL)
> +	field1 = DECL_CHAIN (field1);
> +      while (field2 && TREE_CODE (field2) != FIELD_DECL)
> +	field2 = DECL_CHAIN (field2);
> +      if (field1 && DECL_FIELD_IS_BASE (field1))
> +	{
> +	  if (is_empty_field (field1))
> +	    {
> +	      field1 = DECL_CHAIN (field1);
> +	      continue;
> +	    }
> +	  return is_corresponding_member_aggr (loc, TREE_TYPE (field1),
> +					       membertype1, arg1, basetype2,
> +					       membertype2, arg2);
> +	}
> +      if (field2 && DECL_FIELD_IS_BASE (field2))
> +	{
> +	  if (is_empty_field (field2))
> +	    {
> +	      field2 = DECL_CHAIN (field2);
> +	      continue;
> +	    }
> +	  return is_corresponding_member_aggr (loc, basetype1, membertype1,
> +					       arg1, TREE_TYPE (field2),
> +					       membertype2, arg2);
> +	}
> +      if (field1 == NULL_TREE || field2 == NULL_TREE)
> +	break;
> +      if ((!lookup_attribute ("no_unique_address",
> +			      DECL_ATTRIBUTES (field1)))
> +	  != !lookup_attribute ("no_unique_address",
> +				DECL_ATTRIBUTES (field2)))
> +	break;
> +      if (DECL_/BIT_FIELD_TYPE (field1))
> +	{
> +	  if (!DECL_BIT_FIELD_TYPE (field2))
> +	    break;
> +	  if (!layout_compatible_type_p (DECL_BIT_FIELD_TYPE (field1),
> +					 DECL_BIT_FIELD_TYPE (field2)))
> +	    break;
> +	  if (TYPE_PRECISION (TREE_TYPE (field1))
> +	      != TYPE_PRECISION (TREE_TYPE (field2)))
> +	    break;
> +	}
> +      else if (DECL_BIT_FIELD_TYPE (field2))
> +	break;
> +      else
> +	{
> +	  if (same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (field1),
> +							 membertype1)
> +	      && same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (field2),
> +							    membertype2))
> +	    {
> +	      tree pos1 = byte_position (field1);
> +	      tree pos2 = byte_position (field2);
> +	      if (TREE_CODE (arg1) == INTEGER_CST
> +		  && TREE_CODE (arg2) == INTEGER_CST)
> +		{
> +		  if (tree_int_cst_equal (arg1, pos1)
> +		      && tree_int_cst_equal (arg2, pos2))
> +		    return boolean_true_node;
> +		}
> +	      else if (TREE_CODE (arg1) == INTEGER_CST
> +		       && !tree_int_cst_equal (arg1, pos1))
> +		;
> +	      else if (!tree_int_cst_equal (pos1, pos2))
> +		{
> +		  sorry_at (loc, "%<__builtin_is_corresponding_member%> "
> +				 "unsupported because corresponding members "
> +				 "%qD and %qD have different offsets",
> +				 field1, field2);
> +		  return boolean_false_node;
> +		}
> +	      else if (TREE_CODE (arg1) == INTEGER_CST)
> +		return pos2;
> +	      else
> +		ret = pos1;
> +	    }
> +	  else if (ANON_AGGR_TYPE_P (TREE_TYPE (field1))
> +		   && ANON_AGGR_TYPE_P (TREE_TYPE (field2)))
> +	    {
> +	      bool overlap = true;
> +	      tree pos1 = byte_position (field1);
> +	      tree pos2 = byte_position (field2);
> +	      if (TREE_CODE (arg1) == INTEGER_CST)
> +		{
> +		  tree off1 = fold_convert (sizetype, arg1);
> +		  tree sz1 = TYPE_SIZE_UNIT (TREE_TYPE (field1));
> +		  if (tree_int_cst_lt (off1, pos1)
> +		      || tree_int_cst_le (size_binop (PLUS_EXPR, pos1, sz1),
> +					  off1))
> +		    overlap = false;
> +		}
> +	      if (TREE_CODE (arg2) == INTEGER_CST)
> +		{
> +		  tree off2 = fold_convert (sizetype, arg2);
> +		  tree sz2 = TYPE_SIZE_UNIT (TREE_TYPE (field2));
> +		  if (tree_int_cst_lt (off2, pos2)
> +		      || tree_int_cst_le (size_binop (PLUS_EXPR, pos2, sz2),
> +					  off2))
> +		    overlap = false;
> +		}
> +	      if (overlap
> +		  && NON_UNION_CLASS_TYPE_P (TREE_TYPE (field1))
> +		  && NON_UNION_CLASS_TYPE_P (TREE_TYPE (field2)))
> +		{
> +		  tree narg1 = arg1;
> +		  if (TREE_CODE (arg1) == INTEGER_CST)
> +		    narg1 = size_binop (MINUS_EXPR,
> +					fold_convert (sizetype, arg1), pos1);
> +		  tree narg2 = arg2;
> +		  if (TREE_CODE (arg2) == INTEGER_CST)
> +		    narg2 = size_binop (MINUS_EXPR,
> +					fold_convert (sizetype, arg2), pos2);
> +		  tree t1 = TREE_TYPE (field1);
> +		  tree t2 = TREE_TYPE (field2);
> +		  tree nret
> +		    = is_corresponding_member_aggr (loc, t1, membertype1,
> +						    narg1, t2, membertype2,
> +						    narg2);
> +		  if (nret != boolean_false_node)
> +		    {
> +		      if (nret == boolean_true_node)
> +			return nret;
> +		      if (!tree_int_cst_equal (pos1, pos2))
> +			{
> +			  sorry_at (loc,
> +				    "%<__builtin_is_corresponding_member%> "
> +				    "unsupported because corresponding "
> +				    "members %qD and %qD have different "
> +				    "offsets", field1, field2);
> +			  return boolean_false_node;
> +			}
> +		      if (TREE_CODE (arg1) == INTEGER_CST)
> +			return size_binop (PLUS_EXPR, nret, pos2);
> +		      ret = size_binop (PLUS_EXPR, nret, pos1);
> +		    }
> +		}
> +	      else if (overlap
> +		       && TREE_CODE (TREE_TYPE (field1)) == UNION_TYPE
> +		       && TREE_CODE (TREE_TYPE (field2)) == UNION_TYPE)
> +		{
> +		  tree narg1 = arg1;
> +		  if (TREE_CODE (arg1) == INTEGER_CST)
> +		    narg1 = size_binop (MINUS_EXPR,
> +					fold_convert (sizetype, arg1), pos1);
> +		  tree narg2 = arg2;
> +		  if (TREE_CODE (arg2) == INTEGER_CST)
> +		    narg2 = size_binop (MINUS_EXPR,
> +					fold_convert (sizetype, arg2), pos2);
> +		  if (is_corresponding_member_union (TREE_TYPE (field1),
> +						     membertype1, narg1)
> +		      && is_corresponding_member_union (TREE_TYPE (field2),
> +							membertype2, narg2))
> +		    {
> +		      sorry_at (loc, "%<__builtin_is_corresponding_member%> "
> +				     "not well defined for anonymous unions");
> +		      return boolean_false_node;
> +		    }
> +		}
> +	    }
> +	  if (!layout_compatible_type_p (TREE_TYPE (field1),
> +					 TREE_TYPE (field2)))
> +	    break;
> +	}
> +      field1 = DECL_CHAIN (field1);
> +      field2 = DECL_CHAIN (field2);
> +    }
> +  return ret;
> +}
> +
> +/* Fold __builtin_is_corresponding_member call.  */
> +
> +tree
> +fold_builtin_is_corresponding_member (location_t loc, int nargs,
> +				      tree *args)
> +{
> +  /* Unless users call the builtin directly, the following 3 checks should be
> +     ensured from std::is_corresponding_member function template.  */
> +  if (nargs != 2)
> +    {
> +      error_at (loc, "%<__builtin_is_corresponding_member%> "
> +		     "needs two arguments");
> +      return boolean_false_node;
> +    }
> +  tree arg1 = args[0];
> +  tree arg2 = args[1];
> +  if (error_operand_p (arg1) || error_operand_p (arg2))
> +    return boolean_false_node;
> +  if (!TYPE_PTRMEM_P (TREE_TYPE (arg1))
> +      || !TYPE_PTRMEM_P (TREE_TYPE (arg2)))
> +    {
> +      error_at (loc, "%<__builtin_is_corresponding_member%> "
> +		     "argument is not pointer to member");
> +      return boolean_false_node;
> +    }
> +
> +  if (!TYPE_PTRDATAMEM_P (TREE_TYPE (arg1))
> +      || !TYPE_PTRDATAMEM_P (TREE_TYPE (arg2)))
> +    return boolean_false_node;
> +
> +  tree membertype1 = TREE_TYPE (TREE_TYPE (arg1));
> +  tree basetype1 = TYPE_OFFSET_BASETYPE (TREE_TYPE (arg1));
> +  if (!complete_type_or_else (basetype1, NULL_TREE))
> +    return boolean_false_node;
> +
> +  tree membertype2 = TREE_TYPE (TREE_TYPE (arg2));
> +  tree basetype2 = TYPE_OFFSET_BASETYPE (TREE_TYPE (arg2));
> +  if (!complete_type_or_else (basetype2, NULL_TREE))
> +    return boolean_false_node;
> +
> +  if (!NON_UNION_CLASS_TYPE_P (basetype1)
> +      || !NON_UNION_CLASS_TYPE_P (basetype2)
> +      || !std_layout_type_p (basetype1)
> +      || !std_layout_type_p (basetype2))
> +    return boolean_false_node;
> +
> +  /* If the member types aren't layout compatible, then they
> +     can't be corresponding members.  */
> +  if (!layout_compatible_type_p (membertype1, membertype2))
> +    return boolean_false_node;
> +
> +  if (TREE_CODE (arg1) == PTRMEM_CST)
> +    arg1 = cplus_expand_constant (arg1);
> +  if (TREE_CODE (arg2) == PTRMEM_CST)
> +    arg2 = cplus_expand_constant (arg2);
> +
> +  if (null_member_pointer_value_p (arg1)
> +      || null_member_pointer_value_p (arg2))
> +    return boolean_false_node;
> +
> +  if (TREE_CODE (arg2) == INTEGER_CST
> +      && TREE_CODE (arg1) != INTEGER_CST)
> +    {
> +      std::swap (arg1, arg2);
> +      std::swap (membertype1, membertype2);
> +      std::swap (basetype1, basetype2);
> +    }
> +
> +  tree ret = is_corresponding_member_aggr (loc, basetype1, membertype1, arg1,
> +					   basetype2, membertype2, arg2);
> +  if (TREE_TYPE (ret) == boolean_type_node)
> +    return ret;
> +  gcc_assert (TREE_CODE (arg2) != INTEGER_CST);
> +  if (TREE_CODE (arg1) == INTEGER_CST)
> +    return fold_build2 (EQ_EXPR, boolean_type_node, arg1,
> +			fold_convert (TREE_TYPE (arg1), arg2));
> +  ret = fold_build2 (LE_EXPR, boolean_type_node,
> +		     fold_convert (pointer_sized_int_node, arg1),
> +		     fold_convert (pointer_sized_int_node, ret));

What is this doing?

> +  return fold_build2 (TRUTH_AND_EXPR, boolean_type_node, ret,
> +		      fold_build2 (EQ_EXPR, boolean_type_node, arg1,
> +				   fold_convert (TREE_TYPE (arg1), arg2)));
> +}
> +
>   /* Actually evaluates the trait.  */
>   
>   static bool
> @@ -10760,6 +11062,9 @@ trait_expr_value (cp_trait_kind kind, tr
>       case CPTK_IS_FINAL:
>         return CLASS_TYPE_P (type1) && CLASSTYPE_FINAL (type1);
>   
> +    case CPTK_IS_LAYOUT_COMPATIBLE:
> +      return layout_compatible_type_p (type1, type2);
> +
>       case CPTK_IS_LITERAL_TYPE:
>         return literal_type_p (type1);
>   
> @@ -10907,6 +11212,19 @@ finish_trait_expr (location_t loc, cp_tr
>       case CPTK_IS_SAME_AS:
>         break;
>   
> +    case CPTK_IS_LAYOUT_COMPATIBLE:
> +      if (!array_of_unknown_bound_p (type1)
> +	  && TREE_CODE (type1) != VOID_TYPE
> +	  && !complete_type_or_else (type1, NULL_TREE))
> +	/* We already issued an error.  */
> +	return error_mark_node;
> +      if (!array_of_unknown_bound_p (type2)
> +	  && TREE_CODE (type2) != VOID_TYPE
> +	  && !complete_type_or_else (type2, NULL_TREE))
> +	/* We already issued an error.  */
> +	return error_mark_node;
> +      break;
> +
>       default:
>         gcc_unreachable ();
>       }
> --- gcc/cp/typeck.c.jj	2021-07-28 12:06:00.483928939 +0200
> +++ gcc/cp/typeck.c	2021-08-02 13:43:08.841809583 +0200
> @@ -1621,6 +1621,122 @@ similar_type_p (tree type1, tree type2)
>     return false;
>   }
>   
> +/* Return true if TYPE1 and TYPE2 are layout-compatible types.  */
> +
> +bool
> +layout_compatible_type_p (tree type1, tree type2)
> +{
> +  if (type1 == error_mark_node || type2 == error_mark_node)
> +    return false;
> +  if (type1 == type2)
> +    return true;
> +  if (TREE_CODE (type1) != TREE_CODE (type2))
> +    return false;
> +
> +  type1 = cp_build_qualified_type (type1, TYPE_UNQUALIFIED);
> +  type2 = cp_build_qualified_type (type2, TYPE_UNQUALIFIED);
> +
> +  if (TREE_CODE (type1) == ENUMERAL_TYPE)
> +    return same_type_p (finish_underlying_type (type1),
> +			finish_underlying_type (type2));
> +
> +  if (CLASS_TYPE_P (type1)
> +      && std_layout_type_p (type1)
> +      && std_layout_type_p (type2))
> +    {
> +      tree field1 = TYPE_FIELDS (type1);
> +      tree field2 = TYPE_FIELDS (type2);
> +      if (TREE_CODE (type1) == RECORD_TYPE)
> +	{
> +	  while (1)
> +	    {
> +	      while (field1 && TREE_CODE (field1) != FIELD_DECL)
> +		field1 = DECL_CHAIN (field1);
> +	      while (field2 && TREE_CODE (field2) != FIELD_DECL)
> +		field2 = DECL_CHAIN (field2);
> +	      if (field1 && DECL_FIELD_IS_BASE (field1))
> +		{
> +		  if (is_empty_field (field1))
> +		    {
> +		      field1 = DECL_CHAIN (field1);
> +		      continue;
> +		    }
> +		  return layout_compatible_type_p (TREE_TYPE (field1), type2);
> +		}
> +	      if (field2 && DECL_FIELD_IS_BASE (field2))
> +		{
> +		  if (is_empty_field (field2))
> +		    {
> +		      field2 = DECL_CHAIN (field2);
> +		      continue;
> +		    }
> +		  return layout_compatible_type_p (type1, TREE_TYPE (field2));
> +		}
> +	      if (field1 == NULL_TREE && field2 == NULL_TREE)
> +		return true;
> +	      if (field1 == NULL_TREE || field2 == NULL_TREE)
> +		return false;
> +	      if (DECL_BIT_FIELD_TYPE (field1))
> +		{
> +		  if (!DECL_BIT_FIELD_TYPE (field2))
> +		    return false;
> +		  if (!layout_compatible_type_p (DECL_BIT_FIELD_TYPE (field1),
> +						 DECL_BIT_FIELD_TYPE (field2)))
> +		    return false;
> +		  if (TYPE_PRECISION (TREE_TYPE (field1))
> +		      != TYPE_PRECISION (TREE_TYPE (field2)))
> +		    return false;
> +		}
> +	      else if (DECL_BIT_FIELD_TYPE (field2))
> +		return false;
> +	      else if (!layout_compatible_type_p (TREE_TYPE (field1),
> +						  TREE_TYPE (field2)))
> +		return false;
> +	      if ((!lookup_attribute ("no_unique_address",
> +				      DECL_ATTRIBUTES (field1)))
> +		  != !lookup_attribute ("no_unique_address",
> +					DECL_ATTRIBUTES (field2)))
> +		return false;
> +	      field1 = DECL_CHAIN (field1);
> +	      field2 = DECL_CHAIN (field2);
> +	    }
> +	}
> +      auto_vec<tree, 16> vec;
> +      unsigned int count = 0;
> +      for (; field1; field1 = DECL_CHAIN (field1))
> +	if (TREE_CODE (field1) == FIELD_DECL)
> +	  count++;
> +      for (; field2; field2 = DECL_CHAIN (field2))
> +	if (TREE_CODE (field2) == FIELD_DECL)
> +	  vec.safe_push (field2);
> +      if (count != vec.length ())
> +	return false;
> +      for (field1 = TYPE_FIELDS (type1); field1; field1 = DECL_CHAIN (field1))
> +	{
> +	  if (TREE_CODE (field1) != FIELD_DECL)
> +	    continue;
> +	  unsigned int j;
> +	  tree t1 = DECL_BIT_FIELD_TYPE (field1);
> +	  if (t1 == NULL_TREE)
> +	    t1 = TREE_TYPE (field1);
> +	  FOR_EACH_VEC_ELT (vec, j, field2)
> +	    {
> +	      tree t2 = DECL_BIT_FIELD_TYPE (field2);
> +	      if (t2 == NULL_TREE)
> +		t2 = TREE_TYPE (field2);
> +	      if (layout_compatible_type_p (t1, t2))
> +		break;
> +	    }
> +	  if (j == vec.length ())
> +	    return false;
> +	  vec.unordered_remove (j);
> +	}
> +      return true;
> +    }
> +
> +  return same_type_p (type1, type2);
> +}
> +
>   /* Returns 1 if TYPE1 is at least as qualified as TYPE2.  */
>   
>   bool
> --- gcc/cp/cp-gimplify.c.jj	2021-07-31 09:17:09.217343612 +0200
> +++ gcc/cp/cp-gimplify.c	2021-07-31 18:50:32.293807956 +0200
> @@ -658,12 +658,20 @@ cp_gimplify_expr (tree *expr_p, gimple_s
>   		*expr_p
>   		  = fold_builtin_source_location (EXPR_LOCATION (*expr_p));
>   		break;
> +	      case CP_BUILT_IN_IS_CORRESPONDING_MEMBER:
> +		*expr_p
> +		  = fold_builtin_is_corresponding_member
> +			(EXPR_LOCATION (*expr_p), call_expr_nargs (*expr_p),
> +			 &CALL_EXPR_ARG (*expr_p, 0));
> +		break;
>   	      case CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS:
>   		*expr_p
>   		  = fold_builtin_is_pointer_inverconvertible_with_class
>   			(EXPR_LOCATION (*expr_p), call_expr_nargs (*expr_p),
>   			 &CALL_EXPR_ARG (*expr_p, 0));
>   		break;
> +	      default:
> +		break;
>   	      }
>   	}
>         break;
> @@ -2579,6 +2587,11 @@ cp_fold (tree x)
>   	      case CP_BUILT_IN_SOURCE_LOCATION:
>   		x = fold_builtin_source_location (EXPR_LOCATION (x));
>   		break;
> +	      case CP_BUILT_IN_IS_CORRESPONDING_MEMBER:
> +	        x = fold_builtin_is_corresponding_member
> +			(EXPR_LOCATION (x), call_expr_nargs (x),
> +			 &CALL_EXPR_ARG (x, 0));
> +		break;
>   	      case CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS:
>                   x = fold_builtin_is_pointer_inverconvertible_with_class
>   			(EXPR_LOCATION (x), call_expr_nargs (x),
> --- gcc/cp/tree.c.jj	2021-07-31 09:17:09.229343445 +0200
> +++ gcc/cp/tree.c	2021-07-31 18:51:04.221380038 +0200
> @@ -455,6 +455,7 @@ builtin_valid_in_constant_expr_p (const_
>   	  {
>   	  case CP_BUILT_IN_IS_CONSTANT_EVALUATED:
>   	  case CP_BUILT_IN_SOURCE_LOCATION:
> +	  case CP_BUILT_IN_IS_CORRESPONDING_MEMBER:
>   	  case CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS:
>   	    return true;
>   	  default:
> --- gcc/cp/cxx-pretty-print.c.jj	2021-07-31 09:17:09.219343584 +0200
> +++ gcc/cp/cxx-pretty-print.c	2021-07-31 18:36:19.866232847 +0200
> @@ -2645,6 +2645,9 @@ pp_cxx_trait_expression (cxx_pretty_prin
>       case CPTK_IS_FINAL:
>         pp_cxx_ws_string (pp, "__is_final");
>         break;
> +    case CPTK_IS_LAYOUT_COMPATIBLE:
> +      pp_cxx_ws_string (pp, "__is_layout_compatible");
> +      break;
>       case CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
>         pp_cxx_ws_string (pp, "__is_pointer_interconvertible_base_of");
>         break;
> @@ -2700,6 +2703,7 @@ pp_cxx_trait_expression (cxx_pretty_prin
>   
>     if (kind == CPTK_IS_BASE_OF
>         || kind == CPTK_IS_SAME_AS
> +      || kind == CPTK_IS_LAYOUT_COMPATIBLE
>         || kind == CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF)
>       {
>         pp_cxx_separate_with (pp, ',');
> --- gcc/cp/class.c.jj	2021-07-31 09:17:09.216343626 +0200
> +++ gcc/cp/class.c	2021-07-31 18:35:24.313977398 +0200
> @@ -136,7 +136,6 @@ static bool check_field_decl (tree, tree
>   static void check_field_decls (tree, tree *, int *, int *);
>   static void build_base_fields (record_layout_info, splay_tree, tree *);
>   static void check_methods (tree);
> -static void remove_zero_width_bit_fields (tree);
>   static bool accessible_nvdtor_p (tree);
>   
>   /* Used by find_flexarrays and related functions.  */
> @@ -5754,31 +5753,6 @@ type_build_dtor_call (tree t)
>     return false;
>   }
>   
> -/* Remove all zero-width bit-fields from T.  */
> -
> -static void
> -remove_zero_width_bit_fields (tree t)
> -{
> -  tree *fieldsp;
> -
> -  fieldsp = &TYPE_FIELDS (t);
> -  while (*fieldsp)
> -    {
> -      if (TREE_CODE (*fieldsp) == FIELD_DECL
> -	  && DECL_C_BIT_FIELD (*fieldsp)
> -	  /* We should not be confused by the fact that grokbitfield
> -	     temporarily sets the width of the bit field into
> -	     DECL_BIT_FIELD_REPRESENTATIVE (*fieldsp).
> -	     check_bitfield_decl eventually sets DECL_SIZE (*fieldsp)
> -	     to that width.  */
> -	  && (DECL_SIZE (*fieldsp) == NULL_TREE
> -	      || integer_zerop (DECL_SIZE (*fieldsp))))
> -	*fieldsp = DECL_CHAIN (*fieldsp);
> -      else
> -	fieldsp = &DECL_CHAIN (*fieldsp);
> -    }
> -}
> -
>   /* Returns TRUE iff we need a cookie when dynamically allocating an
>      array whose elements have the indicated class TYPE.  */
>   
> @@ -6770,10 +6744,6 @@ layout_class_type (tree t, tree *virtual
>         normalize_rli (rli);
>       }
>   
> -  /* Delete all zero-width bit-fields from the list of fields.  Now
> -     that the type is laid out they are no longer important.  */
> -  remove_zero_width_bit_fields (t);
> -
>     if (CLASSTYPE_NON_LAYOUT_POD_P (t) || CLASSTYPE_EMPTY_P (t))
>       {
>         /* T needs a different layout as a base (eliding virtual bases
> --- gcc/testsuite/g++.dg/cpp2a/is-corresponding-member1.C.jj	2021-08-02 10:46:43.286747582 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/is-corresponding-member1.C	2021-08-02 13:50:56.416259504 +0200
> @@ -0,0 +1,61 @@
> +// P0466R5
> +// { dg-do compile { target c++20 } }
> +
> +namespace std
> +{
> +template <class S1, class S2, class M1, class M2>
> +constexpr bool
> +is_corresponding_member (M1 S1::*m1, M2 S2::*m2) noexcept
> +{
> +  return __builtin_is_corresponding_member (m1, m2);
> +}
> +}
> +
> +struct A { int a; };
> +struct B { const int b; };
> +struct C { int a; unsigned int b; int f; A c; int : 0; int d; double e; };
> +struct D { const int x; unsigned int y; int g; B z; int u; double w; };
> +struct E { int a; [[no_unique_address]] int b; };
> +struct F { int c; const int d; };
> +struct G { double a; int b; double c; };
> +struct H { const volatile double d; int e : 16; double f; };
> +struct I { const double g; int h : 15; const double i; };
> +struct J : public A {};
> +struct K {};
> +struct L : public K, public B {};
> +union U { int a; };
> +struct V { void foo () {}; };
> +struct W { int a; private: int b; public: int c; };
> +struct Z : public A, public B {};
> +
> +static_assert (std::is_corresponding_member (&A::a, &A::a));
> +static_assert (std::is_corresponding_member (&A::a, &B::b));
> +static_assert (std::is_corresponding_member (&C::a, &D::x));
> +static_assert (std::is_corresponding_member (&C::b, &D::y));
> +static_assert (std::is_corresponding_member (&C::f, &D::g));
> +static_assert (std::is_corresponding_member (&C::c, &D::z));
> +static_assert (!std::is_corresponding_member (&C::d, &D::u));
> +static_assert (!std::is_corresponding_member (&C::e, &D::w));
> +static_assert (!std::is_corresponding_member (&C::f, &D::x));
> +static_assert (!std::is_corresponding_member (&C::a, &D::g));
> +static_assert (std::is_corresponding_member (&E::a, &F::c));
> +static_assert (!std::is_corresponding_member (&E::b, &F::d));
> +static_assert (std::is_corresponding_member (&G::a, &H::d));
> +static_assert (!std::is_corresponding_member (&G::c, &H::f));
> +static_assert (std::is_corresponding_member (&H::d, &I::g));
> +static_assert (!std::is_corresponding_member (&H::f, &I::i));
> +static_assert (std::is_corresponding_member (&J::a, &B::b));
> +static_assert (std::is_corresponding_member<J, B, int, const int> (&J::a, &B::b));
> +static_assert (std::is_corresponding_member (&J::a, &L::b));
> +static_assert (std::is_corresponding_member<J, L, int, const int> (&J::a, &L::b));
> +static_assert (std::is_corresponding_member (&L::b, &B::b));
> +static_assert (std::is_corresponding_member<L, B, const int, const int> (&L::b, &B::b));
> +static_assert (!std::is_corresponding_member (&U::a, &U::a));
> +static_assert (!std::is_corresponding_member (&A::a, (int A::*) nullptr));
> +static_assert (!std::is_corresponding_member ((int A::*) nullptr, &A::a));
> +static_assert (!std::is_corresponding_member ((int A::*) nullptr, (int A::*) nullptr));
> +static_assert (!std::is_corresponding_member (&V::foo, &V::foo));
> +static_assert (!std::is_corresponding_member (&W::a, &W::a));
> +static_assert (!std::is_corresponding_member (&W::c, &W::c));
> +static_assert (std::is_corresponding_member (&Z::a, &Z::b));
> +static_assert (!std::is_corresponding_member<Z, Z, int, const int> (&Z::a, &Z::b));
> --- gcc/testsuite/g++.dg/cpp2a/is-corresponding-member2.C.jj	2021-08-02 13:44:01.104077456 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/is-corresponding-member2.C	2021-08-02 13:59:17.547238521 +0200
> @@ -0,0 +1,158 @@
> +// P0466R5
> +// { dg-do run { target c++20 } }
> +
> +namespace std
> +{
> +template <class S1, class S2, class M1, class M2>
> +constexpr bool
> +is_corresponding_member (M1 S1::*m1, M2 S2::*m2) noexcept
> +{
> +  return __builtin_is_corresponding_member (m1, m2);
> +}
> +}
> +
> +struct A { int a; };
> +struct B { const int b; };
> +struct C { int a; unsigned int b; int f; A c; int : 0; int d; double e; };
> +struct D { const int x; unsigned int y; int g; B z; int u; double w; };
> +struct E { int a; [[no_unique_address]] int b; };
> +struct F { int c; const int d; };
> +struct G { double a; int b; double c; };
> +struct H { const volatile double d; int e : 16; double f; };
> +struct I { const double g; int h : 15; const double i; };
> +struct J : public A {};
> +struct K {};
> +struct L : public K, public B {};
> +union U { int a; };
> +struct V { void foo () {}; };
> +struct W { int a; private: int b; public: int c; };
> +struct Z : public A, public B {};
> +
> +int
> +main ()
> +{
> +  auto t1 = &A::a;
> +  auto t2 = &A::a;
> +  if (!std::is_corresponding_member (t1, t2))
> +    __builtin_abort ();
> +  auto t3 = &A::a;
> +  auto t4 = &B::b;
> +  if (!std::is_corresponding_member (t3, t4))
> +    __builtin_abort ();
> +  auto t5 = &C::a;
> +  auto t6 = &D::x;
> +  if (!std::is_corresponding_member (t5, t6))
> +    __builtin_abort ();
> +  auto t9 = &C::b;
> +  auto t10 = &D::y;
> +  if (!std::is_corresponding_member (t9, t10))
> +    __builtin_abort ();
> +  auto t11 = &C::f;
> +  auto t12 = &D::g;
> +  if (!std::is_corresponding_member (t11, t12))
> +    __builtin_abort ();
> +  auto t13 = &C::c;
> +  auto t14 = &D::z;
> +  if (!std::is_corresponding_member (t13, t14))
> +    __builtin_abort ();
> +  auto t15 = &C::d;
> +  auto t16 = &D::u;
> +  if (std::is_corresponding_member (t15, t16))
> +    __builtin_abort ();
> +  auto t17 = &C::e;
> +  auto t18 = &D::w;
> +  if (std::is_corresponding_member (t17, t18))
> +    __builtin_abort ();
> +  auto t19 = &C::f;
> +  auto t20 = &D::x;
> +  if (std::is_corresponding_member (t19, t20))
> +    __builtin_abort ();
> +  auto t21 = &C::a;
> +  auto t22 = &D::g;
> +  if (std::is_corresponding_member (t21, t22))
> +    __builtin_abort ();
> +  auto t23 = &E::a;
> +  auto t24 = &F::c;
> +  if (!std::is_corresponding_member (t23, t24))
> +    __builtin_abort ();
> +  auto t25 = &E::b;
> +  auto t26 = &F::d;
> +  if (std::is_corresponding_member (t25, t26))
> +    __builtin_abort ();
> +  auto t27 = &G::a;
> +  auto t28 = &H::d;
> +  if (!std::is_corresponding_member (t27, t28))
> +    __builtin_abort ();
> +  auto t29 = &G::c;
> +  auto t30 = &H::f;
> +  if (std::is_corresponding_member (t29, t30))
> +    __builtin_abort ();
> +  auto t31 = &H::d;
> +  auto t32 = &I::g;
> +  if (!std::is_corresponding_member (t31, t32))
> +    __builtin_abort ();
> +  auto t33 = &H::f;
> +  auto t34 = &I::i;
> +  if (std::is_corresponding_member (t33, t34))
> +    __builtin_abort ();
> +  auto t35 = &J::a;
> +  auto t36 = &B::b;
> +  if (!std::is_corresponding_member (t35, t36))
> +    __builtin_abort ();
> +  int J::*t37 = &J::a;
> +  const int B::*t38 = &B::b;
> +  if (!std::is_corresponding_member (t37, t38))
> +    __builtin_abort ();
> +  auto t39 = &J::a;
> +  auto t40 = &L::b;
> +  if (!std::is_corresponding_member (t39, t40))
> +    __builtin_abort ();
> +  int J::*t41 = &J::a;
> +  const int L::*t42 = &L::b;
> +  if (!std::is_corresponding_member (t41, t42))
> +    __builtin_abort ();
> +  auto t43 = &L::b;
> +  auto t44 = &B::b;
> +  if (!std::is_corresponding_member (t43, t44))
> +    __builtin_abort ();
> +  const int L::*t45 = &L::b;
> +  const int B::*t46 = &B::b;
> +  if (!std::is_corresponding_member (t45, t46))
> +    __builtin_abort ();
> +  auto t47 = &U::a;
> +  auto t48 = &U::a;
> +  if (std::is_corresponding_member (t47, t48))
> +    __builtin_abort ();
> +  auto t49 = &A::a;
> +  auto t50 = (int A::*) nullptr;
> +  if (std::is_corresponding_member (t49, t50))
> +    __builtin_abort ();
> +  auto t51 = (int A::*) nullptr;
> +  auto t52 = &A::a;
> +  if (std::is_corresponding_member (t51, t52))
> +    __builtin_abort ();
> +  auto t53 = (int A::*) nullptr;
> +  auto t54 = (int A::*) nullptr;
> +  if (std::is_corresponding_member (t53, t54))
> +    __builtin_abort ();
> +  auto t55 = &V::foo;
> +  auto t56 = &V::foo;
> +  if (std::is_corresponding_member (t55, t56))
> +    __builtin_abort ();
> +  auto t57 = &W::a;
> +  auto t58 = &W::a;
> +  if (std::is_corresponding_member (t57, t58))
> +    __builtin_abort ();
> +  auto t59 = &W::c;
> +  auto t60 = &W::c;
> +  if (std::is_corresponding_member (t59, t60))
> +    __builtin_abort ();
> +  auto t61 = &Z::a;
> +  auto t62 = &Z::b;
> +  if (!std::is_corresponding_member (t61, t62))
> +    __builtin_abort ();
> +  int Z::*t63 = &Z::a;
> +  const int Z::*t64 = &Z::b;
> +  if (std::is_corresponding_member (t63, t64))
> +    __builtin_abort ();
> +}
> --- gcc/testsuite/g++.dg/cpp2a/is-corresponding-member3.C.jj	2021-08-02 12:25:09.226579179 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/is-corresponding-member3.C	2021-08-02 12:36:09.169289764 +0200
> @@ -0,0 +1,14 @@
> +// P0466R5
> +// { dg-do compile { target c++20 } }
> +
> +struct A { int a; };
> +struct B;
> +
> +bool a = __builtin_is_corresponding_member ();			// { dg-error "needs two arguments" }
> +bool b = __builtin_is_corresponding_member (&A::a);		// { dg-error "needs two arguments" }
> +bool c = __builtin_is_corresponding_member (&A::a, &A::a, &A::a);	// { dg-error "needs two arguments" }
> +bool d = __builtin_is_corresponding_member (&A::a, 1);			// { dg-error "argument is not pointer to member" }
> +bool e = __builtin_is_corresponding_member (1.0, &A::a);		// { dg-error "argument is not pointer to member" }
> +bool f = __builtin_is_corresponding_member (1, A{});		// { dg-error "argument is not pointer to member" }
> +bool g = __builtin_is_corresponding_member (&A::a, (int B::*) nullptr);	// { dg-error "invalid use of incomplete type" }
> +bool h = __builtin_is_corresponding_member ((int B::*) nullptr, &A::a);	// { dg-error "invalid use of incomplete type" }
> --- gcc/testsuite/g++.dg/cpp2a/is-corresponding-member4.C.jj	2021-08-02 14:28:20.806813256 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/is-corresponding-member4.C	2021-08-02 15:48:17.521716240 +0200
> @@ -0,0 +1,25 @@
> +// P0466R5
> +// { dg-do compile { target c++20 } }
> +
> +namespace std
> +{
> +template <class S1, class S2, class M1, class M2>
> +constexpr bool
> +is_corresponding_member (M1 S1::*m1, M2 S2::*m2) noexcept
> +{
> +  return __builtin_is_corresponding_member (m1, m2);	// { dg-error "invalid use of incomplete type 'struct B'" }
> +}
> +}
> +
> +struct A { int a; };
> +struct B;
> +constexpr int B::*n = nullptr;
> +constexpr auto a = std::is_corresponding_member (&A::a, n);	// { dg-error "invalid use of incomplete type 'struct B'" }
> +constexpr auto b = std::is_corresponding_member (n, &A::a);	// { dg-error "invalid use of incomplete type 'struct B'" }
> +
> +void
> +foo (int B::*m)
> +{
> +  std::is_corresponding_member (&A::a, m);
> +  std::is_corresponding_member (m, &A::a);
> +}
> --- gcc/testsuite/g++.dg/cpp2a/is-corresponding-member5.C.jj	2021-08-02 14:55:04.796357642 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/is-corresponding-member5.C	2021-08-02 15:37:06.278092615 +0200
> @@ -0,0 +1,95 @@
> +// P0466R5
> +// { dg-do run { target c++20 } }
> +
> +namespace std
> +{
> +template <class S1, class S2, class M1, class M2>
> +constexpr bool
> +is_corresponding_member (M1 S1::*m1, M2 S2::*m2) noexcept
> +{
> +  return __builtin_is_corresponding_member (m1, m2);
> +}
> +}
> +
> +struct S {};
> +struct T {};
> +struct I { int a; };
> +struct alignas(16) J { const int b; };
> +struct K { char b; char s[15]; I c; short d; };
> +struct L { char d; char t[15]; J e; short f; };
> +struct U { int a0; [[no_unique_address]] S a1; [[no_unique_address]] S a2; [[no_unique_address]] S a3; short a4; };
> +struct V { int b0; [[no_unique_address]] S b1; [[no_unique_address]] T b2; [[no_unique_address]] S b3; short b4; };
> +struct U1 { int a0; [[no_unique_address]] S a1; [[no_unique_address]] S a2; [[no_unique_address]] S a3; short a4; };
> +struct V1 { int b0; [[no_unique_address]] S b1; [[no_unique_address]] T b2; [[no_unique_address]] S b3; short b4; };
> +struct A { int a; union { short b; long c; }; int d; signed char e; int f; };
> +struct B { const int a; union { signed long b; short c; }; volatile int d; unsigned char e; int f; };
> +struct A1 { int a; union { short b; long c; }; int d; short e; int f; };
> +struct B1 { const int a; union { signed long b; short c; }; volatile int d; unsigned short e; int f; };
> +
> +static_assert (std::is_corresponding_member (&I::a, &J::b));
> +static_assert (std::is_corresponding_member (&K::b, &L::d));
> +static_assert (std::is_corresponding_member (&K::c, &L::e));	// Questionable
> +static_assert (std::is_corresponding_member (&U::a0, &V::b0));
> +static_assert (std::is_corresponding_member (&U::a4, &V::b4));
> +static_assert (std::is_corresponding_member (&A::a, &B::a));
> +static_assert (std::is_corresponding_member (&A::d, &B::d));
> +static_assert (!std::is_corresponding_member (&A::e, &B::e));
> +static_assert (!std::is_corresponding_member (&A::f, &B::f));
> +static_assert (!std::is_corresponding_member (&A::a, &B::f));
> +static_assert (!std::is_corresponding_member (&A::d, &B::a));
> +static_assert (!std::is_corresponding_member (&A::a, &B::d));
> +static_assert (!std::is_corresponding_member (&A::f, &B::a));
> +static_assert (!std::is_corresponding_member (&A1::e, &B1::e));
> +
> +int
> +main ()
> +{
> +  auto t1 = &I::a;
> +  auto t2 = &J::b;
> +  if (!std::is_corresponding_member (t1, t2))
> +    __builtin_abort ();
> +  auto t3 = &K::b;
> +  auto t4 = &L::d;
> +  if (!std::is_corresponding_member (t3, t4))
> +    __builtin_abort ();
> +  auto t5 = &K::c;
> +  auto t6 = &L::e;
> +  if (!std::is_corresponding_member (t5, t6))	// Questionable
> +    __builtin_abort ();
> +  auto t7 = &U::a0;
> +  auto t8 = &V::b0;
> +  if (!std::is_corresponding_member (t7, t8))
> +    __builtin_abort ();
> +  auto t9 = &U::a4;
> +  auto t10 = &V::b4;
> +  if (!std::is_corresponding_member (t9, t10))
> +    __builtin_abort ();
> +  auto t11 = &A::a;
> +  auto t12 = &B::a;
> +  auto t13 = &A::d;
> +  auto t14 = &B::d;
> +  auto t15 = &A::e;
> +  auto t16 = &B::e;
> +  auto t17 = &A::f;
> +  auto t18 = &B::f;
> +  if (!std::is_corresponding_member (t11, t12))
> +    __builtin_abort ();
> +  if (!std::is_corresponding_member (t13, t14))
> +    __builtin_abort ();
> +  if (std::is_corresponding_member (t15, t16))
> +    __builtin_abort ();
> +  if (std::is_corresponding_member (t17, t18))
> +    __builtin_abort ();
> +  if (std::is_corresponding_member (t11, t18))
> +    __builtin_abort ();
> +  if (std::is_corresponding_member (t13, t12))
> +    __builtin_abort ();
> +  if (std::is_corresponding_member (t11, t14))
> +    __builtin_abort ();
> +  if (std::is_corresponding_member (t17, t12))
> +    __builtin_abort ();
> +  auto t19 = &A1::e;
> +  auto t20 = &B1::e;
> +  if (std::is_corresponding_member (t19, t20))
> +    __builtin_abort ();
> +}
> --- gcc/testsuite/g++.dg/cpp2a/is-corresponding-member6.C.jj	2021-08-02 14:57:41.326168300 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/is-corresponding-member6.C	2021-08-02 15:47:38.961254670 +0200
> @@ -0,0 +1,40 @@
> +// P0466R5
> +// { dg-do compile { target c++20 } }
> +
> +namespace std
> +{
> +template <class S1, class S2, class M1, class M2>
> +constexpr bool
> +is_corresponding_member (M1 S1::*m1, M2 S2::*m2) noexcept
> +{
> +  return __builtin_is_corresponding_member (m1, m2);
> +}
> +// { dg-message "'__builtin_is_corresponding_member' unsupported because corresponding members 'K::d' and 'L::f' have different offsets" "" { target *-*-* } .-2 }
> +// { dg-message "'__builtin_is_corresponding_member' unsupported because corresponding members 'U::a2' and 'V::b2' have different offsets" "" { target *-*-* } .-3 }
> +// { dg-message "'__builtin_is_corresponding_member' unsupported because corresponding members 'U::a3' and 'V::b3' have different offsets" "" { target *-*-* } .-4 }
> +// { dg-message "'__builtin_is_corresponding_member' unsupported because corresponding members 'U1::a3' and 'V1::b3' have different offsets" "" { target *-*-* } .-5 }
> +// { dg-message "'__builtin_is_corresponding_member' not well defined for anonymous unions" "" { target *-*-* } .-6 }
> +}
> +
> +struct S {};
> +struct T {};
> +struct I { int a; };
> +struct alignas(16) J { const int b; };
> +struct K { char b; char s[15]; alignas(16) I c; short d; };
> +struct L { char d; char t[15]; J e; short f; };
> +struct U { int a0; [[no_unique_address]] S a1; [[no_unique_address]] S a2; [[no_unique_address]] S a3; short a4; };
> +struct V { int b0; [[no_unique_address]] S b1; [[no_unique_address]] T b2; [[no_unique_address]] S b3; short b4; };
> +struct U1 { int a0; [[no_unique_address]] S a1; [[no_unique_address]] S a2; [[no_unique_address]] S a3; short a4; };
> +struct V1 { int b0; [[no_unique_address]] S b1; [[no_unique_address]] T b2; [[no_unique_address]] S b3; short b4; };
> +struct A { int a; union { short b; long c; }; int d; signed char e; int f; };
> +struct B { const int a; union { signed long b; short c; }; volatile int d; unsigned char e; int f; };
> +
> +constexpr auto a = std::is_corresponding_member (&K::d, &L::f);		// { dg-message "required from here" }
> +constexpr auto b = std::is_corresponding_member (&U::a1, &V::b1);	// { dg-message "required from here" }
> +constexpr auto c = std::is_corresponding_member (&U::a2, &V::b2);	// { dg-message "required from here" }
> +constexpr auto d = std::is_corresponding_member (&U::a3, &V::b3);	// No "required from here" here, as constexpr evaluation
> +									// doesn't sorry and the template function is already
> +									// instantiated
> +constexpr auto e = std::is_corresponding_member (&U1::a3, &V1::b3);	// { dg-message "required from here" }
> +constexpr auto f = std::is_corresponding_member (&A::b, &B::c);		// { dg-message "required from here" }
> +constexpr auto g = std::is_corresponding_member (&A::c, &B::b);		// { dg-message "required from here" }
> --- gcc/testsuite/g++.dg/cpp2a/is-corresponding-member7.C.jj	2021-08-02 15:51:49.720753228 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/is-corresponding-member7.C	2021-08-02 16:04:18.152328820 +0200
> @@ -0,0 +1,71 @@
> +// P0466R5
> +// { dg-do run { target c++20 } }
> +// { dg-options "" }
> +
> +namespace std
> +{
> +template <class S1, class S2, class M1, class M2>
> +constexpr bool
> +is_corresponding_member (M1 S1::*m1, M2 S2::*m2) noexcept
> +{
> +  return __builtin_is_corresponding_member (m1, m2);
> +}
> +}
> +
> +struct A { int a; struct { int b; short c; long d; }; int : 0; int e; };
> +struct B { const signed int a; struct { int b; signed short c; signed long d; }; volatile int e; };
> +struct C { int a; union { struct { short b; long c; }; long d; short e; }; signed int f; };
> +struct D { int a; union { long b; short c; struct { short d; signed long e; }; }; int f; };
> +
> +static_assert (std::is_corresponding_member (&A::a, &B::a));
> +static_assert (std::is_corresponding_member (&A::b, &B::b));
> +static_assert (std::is_corresponding_member (&A::c, &B::c));
> +static_assert (std::is_corresponding_member (&A::d, &B::d));
> +static_assert (!std::is_corresponding_member (&A::e, &B::e));
> +static_assert (!std::is_corresponding_member (&A::a, &B::b));
> +static_assert (!std::is_corresponding_member (&A::b, &B::a));
> +static_assert (std::is_corresponding_member (&C::a, &D::a));
> +static_assert (std::is_corresponding_member (&C::f, &D::f));
> +static_assert (!std::is_corresponding_member (&C::a, &D::f));
> +static_assert (!std::is_corresponding_member (&C::f, &D::a));
> +
> +int
> +main ()
> +{
> +  auto t1 = &A::a;
> +  auto t2 = &B::a;
> +  auto t3 = &A::b;
> +  auto t4 = &B::b;
> +  auto t5 = &A::c;
> +  auto t6 = &B::c;
> +  auto t7 = &A::d;
> +  auto t8 = &B::d;
> +  auto t9 = &A::e;
> +  auto t10 = &B::e;
> +  if (!std::is_corresponding_member (t1, t2))
> +    __builtin_abort ();
> +  if (!std::is_corresponding_member (t3, t4))
> +    __builtin_abort ();
> +  if (!std::is_corresponding_member (t5, t6))
> +    __builtin_abort ();
> +  if (!std::is_corresponding_member (t7, t8))
> +    __builtin_abort ();
> +  if (std::is_corresponding_member (t9, t10))
> +    __builtin_abort ();
> +  if (std::is_corresponding_member (t1, t4))
> +    __builtin_abort ();
> +  if (std::is_corresponding_member (t3, t2))
> +    __builtin_abort ();
> +  auto t11 = &C::a;
> +  auto t12 = &D::a;
> +  auto t13 = &C::f;
> +  auto t14 = &D::f;
> +  if (!std::is_corresponding_member (t11, t12))
> +    __builtin_abort ();
> +  if (!std::is_corresponding_member (t13, t14))
> +    __builtin_abort ();
> +  if (std::is_corresponding_member (t11, t14))
> +    __builtin_abort ();
> +  if (std::is_corresponding_member (t13, t12))
> +    __builtin_abort ();
> +}
> --- gcc/testsuite/g++.dg/cpp2a/is-corresponding-member8.C.jj	2021-08-02 15:57:25.464067775 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/is-corresponding-member8.C	2021-08-02 16:12:24.067571542 +0200
> @@ -0,0 +1,26 @@
> +// P0466R5
> +// { dg-do compile { target c++20 } }
> +// { dg-options "" }
> +
> +namespace std
> +{
> +template <class S1, class S2, class M1, class M2>
> +constexpr bool
> +is_corresponding_member (M1 S1::*m1, M2 S2::*m2) noexcept
> +{
> +  return __builtin_is_corresponding_member (m1, m2);
> +}
> +// { dg-message "'__builtin_is_corresponding_member' unsupported because corresponding members" "" { target *-*-* } .-2 }
> +// { dg-message "'__builtin_is_corresponding_member' not well defined for anonymous unions" "" { target *-*-* } .-3 }
> +}
> +
> +struct A { int a; struct { short b; short c; long d; }; int : 0; int e; };
> +struct B { const signed int a; struct alignas(16) { short b; signed short c; signed long d; }; volatile int e; };
> +struct C { int a; union { struct { int b; long c; }; long d; short e; }; signed int f; };
> +struct D { int a; union { long b; short c; struct { int d; signed long e; }; }; int f; };
> +
> +static_assert (std::is_corresponding_member (&A::a, &B::a));
> +auto a = std::is_corresponding_member (&A::b, &B::b);		// { dg-message "required from here" }
> +auto b = std::is_corresponding_member (&A::c, &B::c);
> +auto c = std::is_corresponding_member (&A::d, &B::d);		// { dg-message "required from here" }
> +auto d = std::is_corresponding_member (&C::a, &D::a);		// { dg-message "required from here" }
> --- gcc/testsuite/g++.dg/cpp2a/is-layout-compatible1.C.jj	2021-07-31 18:35:24.313977398 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/is-layout-compatible1.C	2021-08-02 13:37:19.860699332 +0200
> @@ -0,0 +1,80 @@
> +// P0466R5
> +// { dg-do compile { target c++20 } }
> +
> +namespace std
> +{
> +template <typename T, T v>
> +struct integral_constant
> +{
> +  static constexpr T value = v;
> +};
> +
> +template <typename, typename>
> +struct is_layout_compatible;
> +
> +template<typename T, typename U>
> +struct is_layout_compatible
> +  : public integral_constant <bool, __is_layout_compatible (T, U)>
> +{
> +};
> +
> +template <typename T, typename U>
> +inline constexpr bool is_layout_compatible_v = __is_layout_compatible (T, U);
> +}
> +
> +struct A { int a; char b; };
> +struct B { const int c; volatile char d; };
> +struct C { int a : 1; int : 7; int : 0; int b : 2; };
> +struct D { int : 1; int c : 7; int : 0; int : 2; };
> +struct E { int f : 1; int : 7; int g : 2; };
> +struct F { int a; signed char b; };
> +union G { int a; long long b; signed char c; unsigned char d; int e; };
> +union H { long long f; unsigned char g; int h; int i; signed char j; };
> +struct I : public A {};
> +struct J {};
> +struct K : public J {};
> +struct L {};
> +struct M : public K, L { const int a; volatile char b; };
> +struct N {};
> +struct O : public N, M {};
> +struct P { int a; private: int b; public: int c; };
> +struct Q { int a; private: int b; public: int c; };
> +union U1 { int a; private: int b; public: int c; };
> +union U2 { int a; private: int b; public: int c; };
> +struct S {};
> +struct T {};
> +struct W;
> +struct X;
> +enum E1 : int { E11, E12 };
> +enum E2 : int { E21, E22 };
> +enum E3 : long { E31, E32 };
> +enum E4 { E41, E42 };
> +enum E5 { E51, E52 };
> +
> +static_assert (std::is_layout_compatible<int, const int>::value);
> +static_assert (std::is_layout_compatible_v<double, volatile double>);
> +static_assert (std::is_layout_compatible_v<A, B>);
> +static_assert (std::is_layout_compatible_v<C, D>);
> +static_assert (!std::is_layout_compatible_v<int, unsigned int>);
> +static_assert (!std::is_layout_compatible_v<A, F>);
> +static_assert (std::is_layout_compatible_v<G, H>);
> +static_assert (std::is_layout_compatible_v<S, T>);
> +static_assert (std::is_layout_compatible_v<A[3], A[3]>);
> +static_assert (std::is_layout_compatible_v<A[], A[]>);
> +static_assert (!std::is_layout_compatible_v<S[1], T[1]>);
> +static_assert (std::is_layout_compatible_v<W[], W[]>);
> +static_assert (!std::is_layout_compatible_v<W[], X[]>);
> +static_assert (!std::is_layout_compatible_v<D, E>);
> +static_assert (std::is_layout_compatible_v<void, const void>);
> +static_assert (std::is_layout_compatible_v<I, const A>);
> +static_assert (std::is_layout_compatible_v<volatile A, const I>);
> +static_assert (std::is_layout_compatible_v<M, A>);
> +static_assert (std::is_layout_compatible_v<O, M>);
> +static_assert (std::is_layout_compatible_v<A, O>);
> +static_assert (std::is_layout_compatible_v<P, P>);
> +static_assert (!std::is_layout_compatible_v<P, Q>);
> +static_assert (std::is_layout_compatible_v<U1, U1>);
> +static_assert (!std::is_layout_compatible_v<U1, U2>);
> +static_assert (std::is_layout_compatible_v<E1, E2>);
> +static_assert (!std::is_layout_compatible_v<E1, E3>);
> +static_assert (std::is_layout_compatible_v<E4, E5>);
> --- gcc/testsuite/g++.dg/cpp2a/is-layout-compatible2.C.jj	2021-07-31 19:17:23.446257425 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/is-layout-compatible2.C	2021-07-31 19:29:54.458235589 +0200
> @@ -0,0 +1,36 @@
> +// P0466R5
> +// { dg-do compile { target c++20 } }
> +
> +namespace std
> +{
> +template <typename T, T v>
> +struct integral_constant
> +{
> +  static constexpr T value = v;
> +};
> +
> +template <typename, typename>
> +struct is_layout_compatible;
> +
> +template<typename T, typename U>
> +struct is_layout_compatible
> +  : public integral_constant <bool, __is_layout_compatible (T, U)>
> +{
> +};
> +
> +template <typename T, typename U>
> +inline constexpr bool is_layout_compatible_v = __is_layout_compatible (T, U);
> +}
> +// { dg-error "invalid use of incomplete type 'struct W'" "" { target *-*-* } .-2 }
> +// { dg-error "invalid use of incomplete type 'struct \[XY]'" "" { target *-*-* } .-3 }
> +// { dg-error "invalid use of incomplete type 'struct Z'" "" { target *-*-* } .-4 }
> +
> +struct W;
> +struct X;
> +struct Y;
> +struct Z;
> +struct A {};
> +
> +auto a = std::is_layout_compatible_v<W, W>;
> +auto b = std::is_layout_compatible_v<X, Y>;
> +auto c = std::is_layout_compatible_v<A, Z>;
> --- gcc/testsuite/g++.dg/cpp2a/is-layout-compatible3.C.jj	2021-08-02 13:05:11.804773825 +0200
> +++ gcc/testsuite/g++.dg/cpp2a/is-layout-compatible3.C	2021-08-02 13:39:24.156957116 +0200
> @@ -0,0 +1,51 @@
> +// P0466R5
> +// { dg-do compile { target c++20 } }
> +
> +namespace std
> +{
> +template <typename T, T v>
> +struct integral_constant
> +{
> +  static constexpr T value = v;
> +};
> +
> +template <typename, typename>
> +struct is_layout_compatible;
> +
> +template<typename T, typename U>
> +struct is_layout_compatible
> +  : public integral_constant <bool, __is_layout_compatible (T, U)>
> +{
> +};
> +
> +template <typename T, typename U>
> +inline constexpr bool is_layout_compatible_v = __is_layout_compatible (T, U);
> +}
> +
> +// Unexpected and weird cases.
> +struct S {};
> +struct T {};
> +struct I { int a; };
> +struct alignas(16) J { const int b; };
> +struct K { I c; int d; };
> +struct L { J e; int f; };
> +union M { I u; };
> +union N { J v; };
> +union O { int a; int b; };
> +union P { int a : 1; int b : 12; };
> +enum Q : int { Q1, Q2 };
> +enum alignas(16) R : int { R1, R2 };
> +struct U { [[no_unique_address]] S a1; [[no_unique_address]] S a2; [[no_unique_address]] S a3; };
> +struct V { [[no_unique_address]] S b1; [[no_unique_address]] T b2; [[no_unique_address]] S b3; };
> +struct alignas(16) A : public I {};
> +struct alignas(16) B {};
> +struct C : public B, public I {};
> +
> +static_assert (std::is_layout_compatible_v<I, J>);
> +static_assert (std::is_layout_compatible_v<K, L>);
> +static_assert (std::is_layout_compatible_v<M, N>);
> +static_assert (std::is_layout_compatible_v<O, P>);
> +static_assert (std::is_layout_compatible_v<Q, R>);
> +static_assert (std::is_layout_compatible_v<U, V>);
> +static_assert (std::is_layout_compatible_v<A, I>);
> +static_assert (std::is_layout_compatible_v<C, I>);
> 
> 	Jakub
> 



More information about the Gcc-patches mailing list