aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gcc/c-family/c-common.c2
-rw-r--r--gcc/c-family/c-common.h1
-rw-r--r--gcc/cp/constexpr.c16
-rw-r--r--gcc/cp/constraint.cc4
-rw-r--r--gcc/cp/cp-gimplify.c50
-rw-r--r--gcc/cp/cp-objcp-common.c1
-rw-r--r--gcc/cp/cp-tree.h3
-rw-r--r--gcc/cp/cxx-pretty-print.c7
-rw-r--r--gcc/cp/decl.c9
-rw-r--r--gcc/cp/parser.c5
-rw-r--r--gcc/cp/semantics.c114
-rw-r--r--gcc/cp/tree.c15
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C55
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C65
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C135
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C11
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C31
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C59
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C19
19 files changed, 578 insertions, 24 deletions
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 21da679..00ac3c5 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 c4b2789..65d8c1c 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 31fa5b6..1af365d 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 4ee5215..e608c5a 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 0520fa4..6e274ac 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 ee25573..beef012 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 ddf8f43..9a47a87 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 3709d0f..b899162 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 71308a0..e4be6be 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 97078f9..ab74e9d 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 f64b084..34e5d76 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 2a14fa9..8345396 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 0000000..d94f18f
--- /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 0000000..efce355
--- /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 0000000..b663a39
--- /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 0000000..b02f1b9
--- /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 0000000..e5e2451
--- /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 0000000..246ff92
--- /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 0000000..4b6801c
--- /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);