public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc r12-2628] c++: Implement P0466R5 __cpp_lib_is_pointer_interconvertible compiler helpers [PR101539]
@ 2021-07-30 16:51 Jakub Jelinek
  0 siblings, 0 replies; only message in thread
From: Jakub Jelinek @ 2021-07-30 16:51 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:6cd005a255f15c1b4b3eaae71c844ea2592c9dce

commit r12-2628-g6cd005a255f15c1b4b3eaae71c844ea2592c9dce
Author: Jakub Jelinek <jakub@redhat.com>
Date:   Fri Jul 30 18:38:41 2021 +0200

    c++: Implement P0466R5 __cpp_lib_is_pointer_interconvertible compiler helpers [PR101539]
    
    The following patch attempts to implement the compiler helpers for
    libstdc++ std::is_pointer_interconvertible_base_of trait and
    std::is_pointer_interconvertible_with_class template function.
    
    For the former __is_pointer_interconvertible_base_of trait that checks first
    whether base and derived aren't non-union class types that are the same
    ignoring toplevel cv-qualifiers, otherwise if derived is unambiguously
    derived from base without cv-qualifiers, derived being a complete type,
    and if so, my limited understanding of any derived object being
    pointer-interconvertible with base subobject IMHO implies (because one can't
    inherit from unions or unions can't inherit) that we check if derived is
    standard layout type and we walk bases of derived
    recursively, stopping on a class that has any non-static data members and
    check if any of the bases is base.  On class with non-static data members
    no bases are compared already.
    Upon discussions, this is something that maybe should have been changed
    in the standard with CWG 2254 and the patch no longer performs this and
    assumes all base subobjects of standard-layout class types are
    pointer-interconvertible with the whole class objects.
    
    The latter is implemented using a FE
    __builtin_is_pointer_interconvertible_with_class, but because on the library
    side it will be a template function, the builtin takes ... arguments and
    only during folding verifies it has a single argument with pointer to member
    type.  The initial errors IMHO can only happen if one uses the builtin
    incorrectly by hand, the template function should ensure that it has
    exactly a single argument that has pointer to member type.
    Otherwise, again with my limited understanding of what
    the template function should do and pointer-interconvertibility,
    it folds to false for pointer-to-member-function, errors if
    basetype of the OFFSET_TYPE is incomplete, folds to false
    for non-std-layout non-union basetype, then finds the first non-static
    data member in the basetype or its bases (by ignoring
    DECL_FIELD_IS_BASE FIELD_DECLs that are empty, recursing into
    DECL_FIELD_IS_BASE FIELD_DECLs type that are non-empty (I think
    std layout should ensure there is at most one), for unions
    checks if membertype is same type as any of the union FIELD_DECLs,
    for non-unions the first other FIELD_DECL only, and for anonymous
    aggregates similarly (union vs. non-union) but recurses into the
    anon aggr types with std layout check for anon structures.  If
    membertype doesn't match the type of first non-static data member
    (or for unions any of the members), then the builtin folds to false,
    otherwise the built folds to a check whether the argument is equal
    to OFFSET_TYPE of 0 or not, either at compile time if it is constant
    (e.g. for constexpr folding) or at runtime otherwise.
    
    As I wrote in the PR, I've tried my testcases with MSVC on godbolt
    that claims to implement it, and https://godbolt.org/z/3PnjM33vM
    for the first testcase shows it disagrees with my expectations on
    static_assert (std::is_pointer_interconvertible_base_of_v<D, F>);
    static_assert (std::is_pointer_interconvertible_base_of_v<E, F>);
    static_assert (!std::is_pointer_interconvertible_base_of_v<D, G>);
    static_assert (!std::is_pointer_interconvertible_base_of_v<D, I>);
    static_assert (std::is_pointer_interconvertible_base_of_v<H, volatile I>);
    Is that a bug in my patch or is MSVC buggy on these (or mix thereof)?
    https://godbolt.org/z/aYeYnne9d
    shows the second testcase, here it differs on:
    static_assert (std::is_pointer_interconvertible_with_class<F, int> (&F::b));
    static_assert (std::is_pointer_interconvertible_with_class<I, int> (&I::g));
    static_assert (std::is_pointer_interconvertible_with_class<L, int> (&L::b));
    static_assert (std::is_pointer_interconvertible_with_class (&V::a));
    static_assert (std::is_pointer_interconvertible_with_class (&V::b));
    Again, my bug, MSVC bug, mix thereof?
    According to Jason the <D, G>, <D, I> case are the subject of the
    CWG 2254 above discussed change and the rest are likely MSVC bugs.
    
    Oh, and there is another thing, the standard has an example:
    struct A { int a; };                    // a standard-layout class
    struct B { int b; };                    // a standard-layout class
    struct C: public A, public B { };       // not a standard-layout class
    
    static_assert( is_pointer_interconvertible_with_class( &C::b ) );
      // Succeeds because, despite its appearance, &C::b has type
      // “pointer to member of B of type int”.
    static_assert( is_pointer_interconvertible_with_class<C>( &C::b ) );
      // Forces the use of class C, and fails.
    It seems to work as written with MSVC (second assertion fails),
    but fails with GCC with the patch:
    /tmp/1.C:22:57: error: no matching function for call to ‘is_pointer_interconvertible_with_class<C>(int B::*)’
       22 | static_assert( is_pointer_interconvertible_with_class<C>( &C::b ) );
          |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
    /tmp/1.C:8:1: note: candidate: ‘template<class S, class M> constexpr bool std::is_pointer_interconvertible_with_class(M S::*)’
        8 | is_pointer_interconvertible_with_class (M S::*m) noexcept
          | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    /tmp/1.C:8:1: note:   template argument deduction/substitution failed:
    /tmp/1.C:22:57: note:   mismatched types ‘C’ and ‘B’
       22 | static_assert( is_pointer_interconvertible_with_class<C>( &C::b ) );
          |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
    the second int argument isn't deduced.
    
    This boils down to:
    template <class S, class M>
    bool foo (M S::*m) noexcept;
    struct A { int a; };
    struct B { int b; };
    struct C : public A, public B {};
    bool a = foo (&C::b);
    bool b = foo<C, int> (&C::b);
    bool c = foo<C> (&C::b);
    which with /std:c++20 or -std=c++20 is accepted by latest MSVC and ICC but
    rejected by GCC and clang (in both cases on the last line).
    Is this a GCC/clang bug in argument deduction (in that case I think we want
    a separate PR), or a bug in ICC/MSVC and the standard itself that should
    specify in the examples both template arguments instead of just the first?
    And this has been raised with the CWG.
    
    2021-07-30  Jakub Jelinek  <jakub@redhat.com>
    
            PR c++/101539
    gcc/c-family/
            * c-common.h (enum rid): Add RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF.
            * c-common.c (c_common_reswords): Add
            __is_pointer_interconvertible_base_of.
    gcc/cp/
            * cp-tree.h (enum cp_trait_kind): Add
            CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF.
            (enum cp_built_in_function): Add
            CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS.
            (fold_builtin_is_pointer_inverconvertible_with_class): Declare.
            * parser.c (cp_parser_primary_expression): Handle
            RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF.
            (cp_parser_trait_expr): Likewise.
            * cp-objcp-common.c (names_builtin_p): Likewise.
            * constraint.cc (diagnose_trait_expr): Handle
            CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF.
            * decl.c (cxx_init_decl_processing): Register
            __builtin_is_pointer_interconvertible_with_class builtin.
            * constexpr.c (cxx_eval_builtin_function_call): Handle
            CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS builtin.
            * semantics.c (pointer_interconvertible_base_of_p,
            first_nonstatic_data_member_p,
            fold_builtin_is_pointer_inverconvertible_with_class): New functions.
            (trait_expr_value): Handle CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF.
            (finish_trait_expr): Likewise.  Formatting fix.
            * cp-gimplify.c (cp_gimplify_expr): Fold
            CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS.  Call
            fndecl_built_in_p just once.
            (cp_fold): Likewise.
            * tree.c (builtin_valid_in_constant_expr_p): Handle
            CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS.  Call
            fndecl_built_in_p just once.
            * cxx-pretty-print.c (pp_cxx_trait_expression): Handle
            CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF.
    gcc/testsuite/
            * g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C: New test.
            * g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C: New test.
            * g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C: New test.
            * g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C: New test.
            * g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C: New test.
            * g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C: New test.
            * g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C: New test.

Diff:
---
 gcc/c-family/c-common.c                            |   2 +
 gcc/c-family/c-common.h                            |   1 +
 gcc/cp/constexpr.c                                 |  16 ++-
 gcc/cp/constraint.cc                               |   4 +
 gcc/cp/cp-gimplify.c                               |  50 ++++++--
 gcc/cp/cp-objcp-common.c                           |   1 +
 gcc/cp/cp-tree.h                                   |   3 +
 gcc/cp/cxx-pretty-print.c                          |   7 +-
 gcc/cp/decl.c                                      |   9 ++
 gcc/cp/parser.c                                    |   5 +
 gcc/cp/semantics.c                                 | 114 ++++++++++++++++-
 gcc/cp/tree.c                                      |  15 ++-
 .../cpp2a/is-pointer-interconvertible-base-of1.C   |  55 +++++++++
 .../is-pointer-interconvertible-with-class1.C      |  65 ++++++++++
 .../is-pointer-interconvertible-with-class2.C      | 135 +++++++++++++++++++++
 .../is-pointer-interconvertible-with-class3.C      |  11 ++
 .../is-pointer-interconvertible-with-class4.C      |  31 +++++
 .../is-pointer-interconvertible-with-class5.C      |  59 +++++++++
 .../is-pointer-interconvertible-with-class6.C      |  19 +++
 19 files changed, 578 insertions(+), 24 deletions(-)

diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 21da679cd3c..00ac3c5278b 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -421,6 +421,8 @@ const struct c_common_resword c_common_reswords[] =
   { "__is_enum",	RID_IS_ENUM,	D_CXXONLY },
   { "__is_final",	RID_IS_FINAL,	D_CXXONLY },
   { "__is_literal_type", RID_IS_LITERAL_TYPE, D_CXXONLY },
+  { "__is_pointer_interconvertible_base_of",
+			RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF, D_CXXONLY },
   { "__is_pod",		RID_IS_POD,	D_CXXONLY },
   { "__is_polymorphic",	RID_IS_POLYMORPHIC, D_CXXONLY },
   { "__is_same",     RID_IS_SAME_AS, D_CXXONLY },
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index c4b2789e644..65d8c1c7cb6 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -174,6 +174,7 @@ enum rid
   RID_IS_BASE_OF,              RID_IS_CLASS,
   RID_IS_EMPTY,                RID_IS_ENUM,
   RID_IS_FINAL,                RID_IS_LITERAL_TYPE,
+  RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF,
   RID_IS_POD,                  RID_IS_POLYMORPHIC,
   RID_IS_SAME_AS,
   RID_IS_STD_LAYOUT,           RID_IS_TRIVIAL,
diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
index 31fa5b66865..1af365d47b9 100644
--- a/gcc/cp/constexpr.c
+++ b/gcc/cp/constexpr.c
@@ -1427,8 +1427,20 @@ cxx_eval_builtin_function_call (const constexpr_ctx *ctx, tree t, tree fun,
       && ctx->call
       && ctx->call->fundef)
     current_function_decl = ctx->call->fundef->decl;
-  new_call = fold_builtin_call_array (EXPR_LOCATION (t), TREE_TYPE (t),
-				      CALL_EXPR_FN (t), nargs, args);
+  if (fndecl_built_in_p (fun,
+			 CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS,
+			 BUILT_IN_FRONTEND))
+    {
+      location_t loc = EXPR_LOCATION (t);
+      if (nargs >= 1)
+	VERIFY_CONSTANT (args[0]);
+      new_call
+	= fold_builtin_is_pointer_inverconvertible_with_class (loc, nargs,
+							       args);
+    }
+  else
+    new_call = fold_builtin_call_array (EXPR_LOCATION (t), TREE_TYPE (t),
+					CALL_EXPR_FN (t), nargs, args);
   current_function_decl = save_cur_fn;
   force_folding_builtin_constant_p = save_ffbcp;
   if (new_call == NULL)
diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 4ee5215df50..e608c5aed1b 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3631,6 +3631,10 @@ diagnose_trait_expr (tree expr, tree args)
     case CPTK_IS_LITERAL_TYPE:
       inform (loc, "  %qT is not a literal type", t1);
       break;
+    case CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
+      inform (loc, "  %qT is not pointer-interconvertible base of %qT",
+	      t1, t2);
+      break;
     case CPTK_IS_POD:
       inform (loc, "  %qT is not a POD type", t1);
       break;
diff --git a/gcc/cp/cp-gimplify.c b/gcc/cp/cp-gimplify.c
index 0520fa45b91..6e274ac62af 100644
--- a/gcc/cp/cp-gimplify.c
+++ b/gcc/cp/cp-gimplify.c
@@ -648,14 +648,23 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
       if (ret != GS_ERROR)
 	{
 	  tree decl = cp_get_callee_fndecl_nofold (*expr_p);
-	  if (decl
-	      && fndecl_built_in_p (decl, CP_BUILT_IN_IS_CONSTANT_EVALUATED,
-				    BUILT_IN_FRONTEND))
-	    *expr_p = boolean_false_node;
-	  else if (decl
-		   && fndecl_built_in_p (decl, CP_BUILT_IN_SOURCE_LOCATION,
-					 BUILT_IN_FRONTEND))
-	    *expr_p = fold_builtin_source_location (EXPR_LOCATION (*expr_p));
+	  if (decl && fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
+	    switch (DECL_FE_FUNCTION_CODE (decl))
+	      {
+	      case CP_BUILT_IN_IS_CONSTANT_EVALUATED:
+		*expr_p = boolean_false_node;
+		break;
+	      case CP_BUILT_IN_SOURCE_LOCATION:
+		*expr_p
+		  = fold_builtin_source_location (EXPR_LOCATION (*expr_p));
+		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;
+	      }
 	}
       break;
 
@@ -2560,11 +2569,26 @@ cp_fold (tree x)
 	    && DECL_DECLARED_CONSTEXPR_P (current_function_decl))
 	  nw = 1;
 
-	/* Defer folding __builtin_is_constant_evaluated.  */
-	if (callee
-	    && fndecl_built_in_p (callee, CP_BUILT_IN_IS_CONSTANT_EVALUATED,
-				  BUILT_IN_FRONTEND))
-	  break;
+	if (callee && fndecl_built_in_p (callee, BUILT_IN_FRONTEND))
+	  {
+	    switch (DECL_FE_FUNCTION_CODE (callee))
+	      {
+		/* Defer folding __builtin_is_constant_evaluated.  */
+	      case CP_BUILT_IN_IS_CONSTANT_EVALUATED:
+		break;
+	      case CP_BUILT_IN_SOURCE_LOCATION:
+		x = fold_builtin_source_location (EXPR_LOCATION (x));
+		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),
+			 &CALL_EXPR_ARG (x, 0));
+		break;
+	      default:
+		break;
+	      }
+	    break;
+	  }
 
 	if (callee
 	    && fndecl_built_in_p (callee, CP_BUILT_IN_SOURCE_LOCATION,
diff --git a/gcc/cp/cp-objcp-common.c b/gcc/cp/cp-objcp-common.c
index ee255732d5a..beef0123b04 100644
--- a/gcc/cp/cp-objcp-common.c
+++ b/gcc/cp/cp-objcp-common.c
@@ -414,6 +414,7 @@ names_builtin_p (const char *name)
     case RID_IS_ENUM:
     case RID_IS_FINAL:
     case RID_IS_LITERAL_TYPE:
+    case RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
     case RID_IS_POD:
     case RID_IS_POLYMORPHIC:
     case RID_IS_SAME_AS:
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index ddf8f43d8d8..9a47a8787d6 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -1366,6 +1366,7 @@ enum cp_trait_kind
   CPTK_IS_ENUM,
   CPTK_IS_FINAL,
   CPTK_IS_LITERAL_TYPE,
+  CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF,
   CPTK_IS_POD,
   CPTK_IS_POLYMORPHIC,
   CPTK_IS_SAME_AS,
@@ -6355,6 +6356,7 @@ struct GTY((chain_next ("%h.next"))) tinst_level {
 enum cp_built_in_function {
   CP_BUILT_IN_IS_CONSTANT_EVALUATED,
   CP_BUILT_IN_INTEGER_PACK,
+  CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS,
   CP_BUILT_IN_SOURCE_LOCATION,
   CP_BUILT_IN_LAST
 };
@@ -7570,6 +7572,7 @@ extern tree baselink_for_fns                    (tree);
 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_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);
 extern tree build_lambda_object			(tree);
diff --git a/gcc/cp/cxx-pretty-print.c b/gcc/cp/cxx-pretty-print.c
index 3709d0f2b2d..b89916206a6 100644
--- a/gcc/cp/cxx-pretty-print.c
+++ b/gcc/cp/cxx-pretty-print.c
@@ -2645,6 +2645,9 @@ pp_cxx_trait_expression (cxx_pretty_printer *pp, tree t)
     case CPTK_IS_FINAL:
       pp_cxx_ws_string (pp, "__is_final");
       break;
+    case CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
+      pp_cxx_ws_string (pp, "__is_pointer_interconvertible_base_of");
+      break;
     case CPTK_IS_POD:
       pp_cxx_ws_string (pp, "__is_pod");
       break;
@@ -2695,7 +2698,9 @@ pp_cxx_trait_expression (cxx_pretty_printer *pp, tree t)
   pp_cxx_left_paren (pp);
   pp->type_id (TRAIT_EXPR_TYPE1 (t));
 
-  if (kind == CPTK_IS_BASE_OF || kind == CPTK_IS_SAME_AS)
+  if (kind == CPTK_IS_BASE_OF
+      || kind == CPTK_IS_SAME_AS
+      || kind == CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF)
     {
       pp_cxx_separate_with (pp, ',');
       pp->type_id (TRAIT_EXPR_TYPE2 (t));
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index 71308a06c63..e4be6be1819 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -4467,6 +4467,15 @@ cxx_init_decl_processing (void)
 			       BUILT_IN_FRONTEND, NULL, NULL_TREE);
   set_call_expr_flags (decl, ECF_CONST | ECF_NOTHROW | ECF_LEAF);
 
+  tree bool_vaftype = build_varargs_function_type_list (boolean_type_node,
+							NULL_TREE);
+  decl
+    = add_builtin_function ("__builtin_is_pointer_interconvertible_with_class",
+			    bool_vaftype,
+			    CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS,
+			    BUILT_IN_FRONTEND, NULL, NULL_TREE);
+  set_call_expr_flags (decl, ECF_CONST | ECF_NOTHROW | ECF_LEAF);
+
   integer_two_node = build_int_cst (NULL_TREE, 2);
 
   /* Guess at the initial static decls size.  */
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 97078f90155..ab74e9d2fb7 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -5799,6 +5799,7 @@ cp_parser_primary_expression (cp_parser *parser,
 	case RID_IS_ENUM:
 	case RID_IS_FINAL:
 	case RID_IS_LITERAL_TYPE:
+	case RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
 	case RID_IS_POD:
 	case RID_IS_POLYMORPHIC:
 	case RID_IS_SAME_AS:
@@ -10688,6 +10689,10 @@ cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
     case RID_IS_LITERAL_TYPE:
       kind = CPTK_IS_LITERAL_TYPE;
       break;
+    case RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
+      kind = CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF;
+      binary = true;
+      break;
     case RID_IS_POD:
       kind = CPTK_IS_POD;
       break;
diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
index f64b084963c..34e5d7610b0 100644
--- a/gcc/cp/semantics.c
+++ b/gcc/cp/semantics.c
@@ -10566,6 +10566,110 @@ classtype_has_nothrow_assign_or_copy_p (tree type, bool assign_p)
   return saw_copy;
 }
 
+/* Return true if DERIVED is pointer interconvertible base of BASE.  */
+
+static bool
+pointer_interconvertible_base_of_p (tree base, tree derived)
+{
+  if (base == error_mark_node || derived == error_mark_node)
+    return false;
+  base = TYPE_MAIN_VARIANT (base);
+  derived = TYPE_MAIN_VARIANT (derived);
+  if (!NON_UNION_CLASS_TYPE_P (base)
+      || !NON_UNION_CLASS_TYPE_P (derived))
+    return false;
+    
+  if (same_type_p (base, derived))
+    return true;
+
+  if (!std_layout_type_p (derived))
+    return false;
+
+  return uniquely_derived_from_p (base, derived);
+}
+
+/* Helper function for fold_builtin_is_pointer_inverconvertible_with_class,
+   return true if MEMBERTYPE is the type of the first non-static data member
+   of TYPE or for unions of any members.  */
+static bool
+first_nonstatic_data_member_p (tree type, tree membertype)
+{
+  for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
+    {
+      if (TREE_CODE (field) != FIELD_DECL)
+	continue;
+      if (DECL_FIELD_IS_BASE (field) && is_empty_field (field))
+	continue;
+      if (DECL_FIELD_IS_BASE (field))
+	return first_nonstatic_data_member_p (TREE_TYPE (field), membertype);
+      if (ANON_AGGR_TYPE_P (TREE_TYPE (field)))
+	{
+	  if ((TREE_CODE (TREE_TYPE (field)) == UNION_TYPE
+	       || std_layout_type_p (TREE_TYPE (field)))
+	      && first_nonstatic_data_member_p (TREE_TYPE (field), membertype))
+	    return true;
+	}
+      else if (same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (field),
+							  membertype))
+	return true;
+      if (TREE_CODE (type) != UNION_TYPE)
+	return false;
+    }
+  return false;
+}
+
+/* Fold __builtin_is_pointer_interconvertible_with_class call.  */
+
+tree
+fold_builtin_is_pointer_inverconvertible_with_class (location_t loc, int nargs,
+						     tree *args)
+{
+  /* Unless users call the builtin directly, the following 3 checks should be
+     ensured from std::is_pointer_interconvertible_with_class function
+     template.  */
+  if (nargs != 1)
+    {
+      error_at (loc, "%<__builtin_is_pointer_interconvertible_with_class%> "
+		     "needs a single argument");
+      return boolean_false_node;
+    }
+  tree arg = args[0];
+  if (error_operand_p (arg))
+    return boolean_false_node;
+  if (!TYPE_PTRMEM_P (TREE_TYPE (arg)))
+    {
+      error_at (loc, "%<__builtin_is_pointer_interconvertible_with_class%> "
+		     "argument is not pointer to member");
+      return boolean_false_node;
+    }
+
+  if (!TYPE_PTRDATAMEM_P (TREE_TYPE (arg)))
+    return boolean_false_node;
+
+  tree membertype = TREE_TYPE (TREE_TYPE (arg));
+  tree basetype = TYPE_OFFSET_BASETYPE (TREE_TYPE (arg));
+  if (!complete_type_or_else (basetype, NULL_TREE))
+    return boolean_false_node;
+
+  if (TREE_CODE (basetype) != UNION_TYPE
+      && !std_layout_type_p (basetype))
+    return boolean_false_node;
+
+  if (!first_nonstatic_data_member_p (basetype, membertype))
+    return boolean_false_node;
+
+  if (TREE_CODE (arg) == PTRMEM_CST)
+    arg = cplus_expand_constant (arg);
+
+  if (integer_nonzerop (arg))
+    return boolean_false_node;
+  if (integer_zerop (arg))
+    return boolean_true_node;
+
+  return fold_build2 (EQ_EXPR, boolean_type_node, arg,
+		      build_zero_cst (TREE_TYPE (arg)));
+}
+
 /* Actually evaluates the trait.  */
 
 static bool
@@ -10659,6 +10763,9 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2)
     case CPTK_IS_LITERAL_TYPE:
       return literal_type_p (type1);
 
+    case CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
+      return pointer_interconvertible_base_of_p (type1, type2);
+
     case CPTK_IS_POD:
       return pod_type_p (type1);
 
@@ -10786,6 +10893,7 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
       break;
 
     case CPTK_IS_BASE_OF:
+    case CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
       if (NON_UNION_CLASS_TYPE_P (type1) && NON_UNION_CLASS_TYPE_P (type2)
 	  && !same_type_ignoring_top_level_qualifiers_p (type1, type2)
 	  && !complete_type_or_else (type2, NULL_TREE))
@@ -10803,9 +10911,9 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
       gcc_unreachable ();
     }
 
-tree val = (trait_expr_value (kind, type1, type2)
-	    ? boolean_true_node : boolean_false_node);
- return maybe_wrap_with_location (val, loc);
+  tree val = (trait_expr_value (kind, type1, type2)
+	      ? boolean_true_node : boolean_false_node);
+  return maybe_wrap_with_location (val, loc);
 }
 
 /* Do-nothing variants of functions to handle pragma FLOAT_CONST_DECIMAL64,
diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c
index 2a14fa92ddb..8345396ec33 100644
--- a/gcc/cp/tree.c
+++ b/gcc/cp/tree.c
@@ -450,11 +450,16 @@ builtin_valid_in_constant_expr_p (const_tree decl)
     return false;
   if (DECL_BUILT_IN_CLASS (decl) != BUILT_IN_NORMAL)
     {
-      if (fndecl_built_in_p (decl, CP_BUILT_IN_IS_CONSTANT_EVALUATED,
-			     BUILT_IN_FRONTEND)
-	  || fndecl_built_in_p (decl, CP_BUILT_IN_SOURCE_LOCATION,
-				BUILT_IN_FRONTEND))
-	return true;
+      if (fndecl_built_in_p (decl, BUILT_IN_FRONTEND))
+	switch (DECL_FE_FUNCTION_CODE (decl))
+	  {
+	  case CP_BUILT_IN_IS_CONSTANT_EVALUATED:
+	  case CP_BUILT_IN_SOURCE_LOCATION:
+	  case CP_BUILT_IN_IS_POINTER_INTERCONVERTIBLE_WITH_CLASS:
+	    return true;
+	  default:
+	    break;
+	  }
       /* Not a built-in.  */
       return false;
     }
diff --git a/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C
new file mode 100644
index 00000000000..d94f18fbe8a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C
@@ -0,0 +1,55 @@
+// 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_pointer_interconvertible_base_of;
+
+template<typename T, typename U>
+struct is_pointer_interconvertible_base_of
+  : public integral_constant <bool, __is_pointer_interconvertible_base_of (T, U)>
+{
+};
+
+template <typename T, typename U>
+inline constexpr bool is_pointer_interconvertible_base_of_v = __is_pointer_interconvertible_base_of (T, U);
+}
+
+struct A;
+struct B { int b; };
+struct C : virtual B { int c; };
+struct D {};
+struct E {};
+struct F : public B, D, E {};
+struct G : public D, E { int g; };
+struct H {};
+struct I : public G, H {};
+struct J { int j1; private: int j2; };
+struct K : public J {};
+union U { int a; };
+
+static_assert (std::is_pointer_interconvertible_base_of<A, A>::value);
+static_assert (std::is_pointer_interconvertible_base_of_v<A, A>);
+static_assert (std::is_pointer_interconvertible_base_of_v<const A, volatile A>);
+static_assert (std::is_pointer_interconvertible_base_of_v<B, const B>);
+static_assert (std::is_pointer_interconvertible_base_of_v<C, const volatile C>);
+static_assert (!std::is_pointer_interconvertible_base_of_v<D, E>);
+static_assert (!std::is_pointer_interconvertible_base_of_v<D, const B>);
+static_assert (std::is_pointer_interconvertible_base_of_v<const B, F>);
+static_assert (std::is_pointer_interconvertible_base_of_v<D, const F>);
+static_assert (std::is_pointer_interconvertible_base_of_v<E, F>);
+static_assert (std::is_pointer_interconvertible_base_of_v<D, volatile G>);
+static_assert (std::is_pointer_interconvertible_base_of_v<const E, volatile G>);
+static_assert (std::is_pointer_interconvertible_base_of_v<D, I>);
+static_assert (std::is_pointer_interconvertible_base_of_v<const E, const I>);
+static_assert (std::is_pointer_interconvertible_base_of_v<G, I>);
+static_assert (std::is_pointer_interconvertible_base_of_v<H, volatile I>);
+static_assert (!std::is_pointer_interconvertible_base_of_v<volatile J, const K>);
+static_assert (!std::is_pointer_interconvertible_base_of_v<U, U>);
diff --git a/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C
new file mode 100644
index 00000000000..efce355f4ea
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C
@@ -0,0 +1,65 @@
+// P0466R5
+// { dg-do compile { target c++20 } }
+
+namespace std
+{
+template <class S, class M>
+constexpr bool
+is_pointer_interconvertible_with_class (M S::*m) noexcept
+{
+  return __builtin_is_pointer_interconvertible_with_class (m);
+}
+}
+
+struct A;
+struct B { int b; double b2; };
+struct C : virtual B { int c; };
+struct D {};
+struct E {};
+struct F : public B, D, E {};
+struct G : public D, E { int g; };
+struct H {};
+struct I : public G, H {};
+struct J { int j1; private: int j2; public: int j3; };
+struct K : public J {};
+struct L : public B, D, E {};
+struct M { D d [[no_unique_address]]; E e [[no_unique_address]]; int f; };
+union U { int a; double b; long long c; };
+struct V { union { int a; long b; }; int c; };
+union X { int a; union { short b; long c; }; long long d; };
+struct Y { void foo () {} };
+union Z { int a; private: int b; protected: int c; public: int d; };
+
+static_assert (std::is_pointer_interconvertible_with_class (&B::b));
+static_assert (!std::is_pointer_interconvertible_with_class (&B::b2));
+static_assert (std::is_pointer_interconvertible_with_class (&C::b));
+static_assert (std::is_pointer_interconvertible_with_class (&F::b));
+static_assert (std::is_pointer_interconvertible_with_class<F, int> (&F::b));
+static_assert (std::is_pointer_interconvertible_with_class (&G::g));
+static_assert (std::is_pointer_interconvertible_with_class<G, int> (&G::g));
+static_assert (std::is_pointer_interconvertible_with_class (&I::g));
+static_assert (std::is_pointer_interconvertible_with_class<I, int> (&I::g));
+static_assert (!std::is_pointer_interconvertible_with_class (&J::j1));
+static_assert (!std::is_pointer_interconvertible_with_class (&J::j3));
+static_assert (!std::is_pointer_interconvertible_with_class (&K::j1));
+static_assert (!std::is_pointer_interconvertible_with_class (&K::j3));
+static_assert (std::is_pointer_interconvertible_with_class (&L::b));
+static_assert (std::is_pointer_interconvertible_with_class<L, int> (&L::b));
+static_assert (std::is_pointer_interconvertible_with_class (&L::b));
+static_assert (std::is_pointer_interconvertible_with_class (&M::d));
+static_assert (!std::is_pointer_interconvertible_with_class (&M::e));
+static_assert (!std::is_pointer_interconvertible_with_class (&M::f));
+static_assert (std::is_pointer_interconvertible_with_class (&U::a));
+static_assert (std::is_pointer_interconvertible_with_class (&U::b));
+static_assert (std::is_pointer_interconvertible_with_class (&U::c));
+static_assert (std::is_pointer_interconvertible_with_class (&V::a));
+static_assert (std::is_pointer_interconvertible_with_class (&V::b));
+static_assert (!std::is_pointer_interconvertible_with_class (&V::c));
+static_assert (std::is_pointer_interconvertible_with_class (&X::a));
+static_assert (std::is_pointer_interconvertible_with_class (&X::b));
+static_assert (std::is_pointer_interconvertible_with_class (&X::c));
+static_assert (std::is_pointer_interconvertible_with_class (&X::d));
+static_assert (!std::is_pointer_interconvertible_with_class ((int B::*) nullptr));
+static_assert (!std::is_pointer_interconvertible_with_class (&Y::foo));
+static_assert (std::is_pointer_interconvertible_with_class (&Z::a));
+static_assert (std::is_pointer_interconvertible_with_class (&Z::d));
diff --git a/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C
new file mode 100644
index 00000000000..b663a3933cf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C
@@ -0,0 +1,135 @@
+// P0466R5
+// { dg-do run { target c++20 } }
+
+namespace std
+{
+template <class S, class M>
+constexpr bool
+is_pointer_interconvertible_with_class (M S::*m) noexcept
+{
+  return __builtin_is_pointer_interconvertible_with_class (m);
+}
+}
+
+struct A;
+struct B { int b; double b2; };
+struct C : virtual B { int c; };
+struct D {};
+struct E {};
+struct F : public B, D, E {};
+struct G : public D, E { int g; };
+struct H {};
+struct I : public G, H {};
+struct J { int j1; private: int j2; public: int j3; };
+struct K : public J {};
+struct L : public B, D, E {};
+struct M { D d [[no_unique_address]]; E e [[no_unique_address]]; int f; };
+union U { int a; double b; long long c; };
+struct V { union { int a; long b; }; int c; };
+union X { int a; union { short b; long c; }; long long d; };
+struct Y { void foo () {} };
+union Z { int a; private: int b; protected: int c; public: int d; };
+
+int
+main ()
+{
+  auto t1 = &B::b;
+  if (!std::is_pointer_interconvertible_with_class (t1))
+    __builtin_abort ();
+  auto t2 = &B::b2;
+  if (std::is_pointer_interconvertible_with_class (t2))
+    __builtin_abort ();
+  auto t3 = &C::b;
+  if (!std::is_pointer_interconvertible_with_class (t3))
+    __builtin_abort ();
+  auto t4 = &F::b;
+  if (!std::is_pointer_interconvertible_with_class (t4))
+    __builtin_abort ();
+  int F::*t5 = &F::b;
+  if (!std::is_pointer_interconvertible_with_class (t5))
+    __builtin_abort ();
+  auto t6 = &G::g;
+  if (!std::is_pointer_interconvertible_with_class (t6))
+    __builtin_abort ();
+  int G::*t7 = &G::g;
+  if (!std::is_pointer_interconvertible_with_class (t7))
+    __builtin_abort ();
+  auto t8 = &I::g;
+  if (!std::is_pointer_interconvertible_with_class (t8))
+    __builtin_abort ();
+  int I::*t9 = &I::g;
+  if (!std::is_pointer_interconvertible_with_class (t9))
+    __builtin_abort ();
+  auto t10 = &J::j1;
+  if (std::is_pointer_interconvertible_with_class (t10))
+    __builtin_abort ();
+  auto t11 = &J::j3;
+  if (std::is_pointer_interconvertible_with_class (t11))
+    __builtin_abort ();
+  auto t12 = &K::j1;
+  if (std::is_pointer_interconvertible_with_class (t12))
+    __builtin_abort ();
+  auto t13 = &K::j3;
+  if (std::is_pointer_interconvertible_with_class (t13))
+    __builtin_abort ();
+  auto t14 = &L::b;
+  if (!std::is_pointer_interconvertible_with_class (t14))
+    __builtin_abort ();
+  int L::*t15 = &L::b;
+  if (!std::is_pointer_interconvertible_with_class (t15))
+    __builtin_abort ();
+  auto t16 = &L::b;
+  if (!std::is_pointer_interconvertible_with_class (t16))
+    __builtin_abort ();
+  auto t17 = &M::d;
+  if (!std::is_pointer_interconvertible_with_class (t17))
+    __builtin_abort ();
+  auto t18 = &M::e;
+  if (std::is_pointer_interconvertible_with_class (t18))
+    __builtin_abort ();
+  auto t19 = &M::f;
+  if (std::is_pointer_interconvertible_with_class (t19))
+    __builtin_abort ();
+  auto t20 = &U::a;
+  if (!std::is_pointer_interconvertible_with_class (t20))
+    __builtin_abort ();
+  auto t21 = &U::b;
+  if (!std::is_pointer_interconvertible_with_class (t21))
+    __builtin_abort ();
+  auto t22 = &U::c;
+  if (!std::is_pointer_interconvertible_with_class (t22))
+    __builtin_abort ();
+  auto t23 = &V::a;
+  if (!std::is_pointer_interconvertible_with_class (t23))
+    __builtin_abort ();
+  auto t24 = &V::b;
+  if (!std::is_pointer_interconvertible_with_class (t24))
+    __builtin_abort ();
+  auto t25 = &V::c;
+  if (std::is_pointer_interconvertible_with_class (t25))
+    __builtin_abort ();
+  auto t26 = &X::a;
+  if (!std::is_pointer_interconvertible_with_class (t26))
+    __builtin_abort ();
+  auto t27 = &X::b;
+  if (!std::is_pointer_interconvertible_with_class (t27))
+    __builtin_abort ();
+  auto t28 = &X::c;
+  if (!std::is_pointer_interconvertible_with_class (t28))
+    __builtin_abort ();
+  auto t29 = &X::d;
+  if (!std::is_pointer_interconvertible_with_class (t29))
+    __builtin_abort ();
+  auto t30 = (int B::*) nullptr;
+  if (std::is_pointer_interconvertible_with_class (t30))
+    __builtin_abort ();
+  auto t31 = &Y::foo;
+  if (std::is_pointer_interconvertible_with_class (t31))
+    __builtin_abort ();
+  auto t32 = &Z::a;
+  if (!std::is_pointer_interconvertible_with_class (t32))
+    __builtin_abort ();
+  auto t33 = &Z::d;
+  if (!std::is_pointer_interconvertible_with_class (t33))
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C
new file mode 100644
index 00000000000..b02f1b96d9f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C
@@ -0,0 +1,11 @@
+// P0466R5
+// { dg-do compile { target c++20 } }
+
+struct A { int a; };
+struct B;
+
+bool a = __builtin_is_pointer_interconvertible_with_class ();			// { dg-error "needs a single argument" }
+bool b = __builtin_is_pointer_interconvertible_with_class (&A::a, &A::a);	// { dg-error "needs a single argument" }
+bool c = __builtin_is_pointer_interconvertible_with_class (1);			// { dg-error "argument is not pointer to member" }
+bool d = __builtin_is_pointer_interconvertible_with_class (1.0);		// { dg-error "argument is not pointer to member" }
+bool e = __builtin_is_pointer_interconvertible_with_class ((int B::*) nullptr);	// { dg-error "invalid use of incomplete type" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C
new file mode 100644
index 00000000000..e5e24513dd0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C
@@ -0,0 +1,31 @@
+// P0466R5
+// { dg-do compile { target c++20 } }
+// { dg-options "" }
+
+namespace std
+{
+template <class S, class M>
+constexpr bool
+is_pointer_interconvertible_with_class (M S::*m) noexcept
+{
+  return __builtin_is_pointer_interconvertible_with_class (m);
+}
+}
+
+struct W { struct { int a; long b; }; int c; };
+union X { int a; struct { short b; long c; }; long long d; };
+struct D { int x; private: int y; };                                                                                                                                                  
+union Y { int a; struct { short b; long c; D z; }; long long d; };                                                                                                                    
+
+static_assert (std::is_pointer_interconvertible_with_class (&W::a));
+static_assert (!std::is_pointer_interconvertible_with_class (&W::b));
+static_assert (!std::is_pointer_interconvertible_with_class (&W::c));
+static_assert (std::is_pointer_interconvertible_with_class (&X::a));
+static_assert (std::is_pointer_interconvertible_with_class (&X::b));
+static_assert (!std::is_pointer_interconvertible_with_class (&X::c));
+static_assert (std::is_pointer_interconvertible_with_class (&X::d));
+static_assert (std::is_pointer_interconvertible_with_class (&Y::a));
+static_assert (!std::is_pointer_interconvertible_with_class (&Y::b));
+static_assert (!std::is_pointer_interconvertible_with_class (&Y::c));
+static_assert (!std::is_pointer_interconvertible_with_class (&Y::z));
+static_assert (std::is_pointer_interconvertible_with_class (&Y::d));
diff --git a/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C
new file mode 100644
index 00000000000..246ff92ade5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C
@@ -0,0 +1,59 @@
+// P0466R5
+// { dg-do run { target c++20 } }
+// { dg-options "" }
+
+namespace std
+{
+template <class S, class M>
+constexpr bool
+is_pointer_interconvertible_with_class (M S::*m) noexcept
+{
+  return __builtin_is_pointer_interconvertible_with_class (m);
+}
+}
+
+struct W { struct { int a; long b; }; int c; };
+union X { int a; struct { short b; long c; }; long long d; };
+struct D { int x; private: int y; };                                                                                                                                                  
+union Y { int a; struct { short b; long c; D z; }; long long d; };                                                                                                                    
+
+int
+main ()
+{
+  auto t1 = &W::a;
+  if (!std::is_pointer_interconvertible_with_class (t1))
+    __builtin_abort ();
+  auto t2 = &W::b;
+  if (std::is_pointer_interconvertible_with_class (t2))
+    __builtin_abort ();
+  auto t3 = &W::c;
+  if (std::is_pointer_interconvertible_with_class (t3))
+    __builtin_abort ();
+  auto t4 = &X::a;
+  if (!std::is_pointer_interconvertible_with_class (t4))
+    __builtin_abort ();
+  auto t5 = &X::b;
+  if (!std::is_pointer_interconvertible_with_class (t5))
+    __builtin_abort ();
+  auto t6 = &X::c;
+  if (std::is_pointer_interconvertible_with_class (t6))
+    __builtin_abort ();
+  auto t7 = &X::d;
+  if (!std::is_pointer_interconvertible_with_class (t7))
+    __builtin_abort ();
+  auto t8 = &Y::a;
+  if (!std::is_pointer_interconvertible_with_class (t8))
+    __builtin_abort ();
+  auto t9 = &Y::b;
+  if (std::is_pointer_interconvertible_with_class (t9))
+    __builtin_abort ();
+  auto t10 = &Y::c;
+  if (std::is_pointer_interconvertible_with_class (t10))
+    __builtin_abort ();
+  auto t11 = &Y::z;
+  if (std::is_pointer_interconvertible_with_class (t11))
+    __builtin_abort ();
+  auto t12 = &Y::d;
+  if (!std::is_pointer_interconvertible_with_class (t12))
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C
new file mode 100644
index 00000000000..4b6801c41da
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C
@@ -0,0 +1,19 @@
+// P0466R5
+// { dg-do compile { target c++20 } }
+
+namespace std
+{
+template <class S, class M>
+constexpr bool
+is_pointer_interconvertible_with_class (M S::*m) noexcept
+{
+  return __builtin_is_pointer_interconvertible_with_class (m);
+}
+}
+
+struct A { int a; };
+
+double A::*a = nullptr;
+constexpr double A::*b = nullptr;
+constexpr auto c = std::is_pointer_interconvertible_with_class (a);	// { dg-error "is not usable in a constant expression" }
+constexpr auto d = std::is_pointer_interconvertible_with_class (b);


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2021-07-30 16:51 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-07-30 16:51 [gcc r12-2628] c++: Implement P0466R5 __cpp_lib_is_pointer_interconvertible compiler helpers [PR101539] Jakub Jelinek

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