diff mbox series

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

Message ID 20210803070515.GX2380545@tucnak
State New
Headers show
Series c++: Implement P0466R5 __cpp_lib_is_layout_compatible compiler helpers [PR101539] | expand

Commit Message

Jakub Jelinek Aug. 3, 2021, 7:05 a.m. UTC
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.

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).
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.

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.


	Jakub

Comments

Jason Merrill Aug. 12, 2021, 2:33 p.m. UTC | #1
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
>
Jakub Jelinek Aug. 12, 2021, 3:16 p.m. UTC | #2
On Thu, Aug 12, 2021 at 10:33:20AM -0400, Jason Merrill wrote:
> > 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.

Ok, so you prefer to change layout_compatible_type_p in anticipation of the
future DR.
Given the g++.dg/cpp2a/is-layout-compatible3.C cases, shall that include:
  if (TYPE_ALIGN (type1) != TYPE_ALIGN (type2))
    return false;  /* Types with different alignment aren't layout-compatible.  */
  if (!tree_int_cst_equal (TYPE_SIZE_UNIT (type1), TYPE_SIZE_UNIT (type2)))
    return false;  /* Types with different sizes aren't layout-compatible.  */
cases inside both the ENUMERAL_TYPE and CLASS_TYPE_P ifs (as e.g. the
enumeral types can have the same underlying type including alignment and
size, but the enumeral type itself could have different alignas)?
I think I can't compare TYPE_SIZE_UNIT for the fallthrough same_type_p case
because the type could be array with unspecified bounds and I expect
same_type_p fails if the sizes or alignments are different.

And then also compare field offsets in struct and say members with different
offsets aren't part of the common initial sequence (that would cover the
struct S {};
struct T {};
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; };
case or alignas on the members as opposed to types)?
What about DECL_ALIGN?  Shall that be relevant even when it doesn't change
anything further (TYPE_ALIGN of the whole struct is already the same and
field with higher DECL_ALIGN has the same field offset as in another struct
where it has that offset because of the previous fields or is at start)?

And finally, what about the union case?  Shall it check also bitfield vs.
non-bitfield, bitfield size if bitfields?  What about [[no_unique_address]]
on the union members, shall that be relevant or not?  And DECL_ALIGN?
E.g. the whole union can have the alignment,
union alignas (16) A { short a; alignas (8) int b; };
vs.
union alignas (16) B { int a; short b; };
All union fields have the same field offset of course (0), but above A::b
has different alignment requirement than B::a.

	Jakub
Jason Merrill Aug. 12, 2021, 4:06 p.m. UTC | #3
On 8/12/21 11:16 AM, Jakub Jelinek wrote:
> On Thu, Aug 12, 2021 at 10:33:20AM -0400, Jason Merrill wrote:
>>> 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.
> 
> Ok, so you prefer to change layout_compatible_type_p in anticipation of the
> future DR.

Yes; if the standard says something nonsensical, I prefer to figure out 
something more sensible to propose as a change.

> Given the g++.dg/cpp2a/is-layout-compatible3.C cases, shall that include:
>    if (TYPE_ALIGN (type1) != TYPE_ALIGN (type2))
>      return false;  /* Types with different alignment aren't layout-compatible.  */
>    if (!tree_int_cst_equal (TYPE_SIZE_UNIT (type1), TYPE_SIZE_UNIT (type2)))
>      return false;  /* Types with different sizes aren't layout-compatible.  */
> cases inside both the ENUMERAL_TYPE and CLASS_TYPE_P ifs (as e.g. the
> enumeral types can have the same underlying type including alignment and
> size, but the enumeral type itself could have different alignas)?
> I think I can't compare TYPE_SIZE_UNIT for the fallthrough same_type_p case
> because the type could be array with unspecified bounds and I expect
> same_type_p fails if the sizes or alignments are different.

Sounds good.

> And then also compare field offsets in struct and say members with different
> offsets aren't part of the common initial sequence (that would cover the
> struct S {};
> struct T {};
> 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; };
> case or alignas on the members as opposed to types)?

Yes.

> What about DECL_ALIGN?  Shall that be relevant even when it doesn't change
> anything further (TYPE_ALIGN of the whole struct is already the same and
> field with higher DECL_ALIGN has the same field offset as in another struct
> where it has that offset because of the previous fields or is at start)?

I'm inclined to accept that.

> And finally, what about the union case?  Shall it check also bitfield vs.
> non-bitfield, bitfield size if bitfields?

Yes.

> What about [[no_unique_address]]
> on the union members, shall that be relevant or not?  And DECL_ALIGN?
> E.g. the whole union can have the alignment,
> union alignas (16) A { short a; alignas (8) int b; };
> vs.
> union alignas (16) B { int a; short b; };
> All union fields have the same field offset of course (0), but above A::b
> has different alignment requirement than B::a.

I'd allow these differences.

Jason
diff mbox series

Patch

--- 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));
+  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>);