Message ID | 20210729075010.GX2380545@tucnak |
---|---|
State | New |
Headers | show |
Series | c++: Implement P0466R5 __cpp_lib_is_pointer_interconvertible compiler helpers [PR101539] | expand |
On 7/29/21 3:50 AM, Jakub Jelinek wrote: > Hi! > > 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. > > 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 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. 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? > > 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? I'll look into this. > Now that I'm writing the above text and rereading the > pointer-interconvertibility definition, I think my first_nonstatic_data_member_p > and fold_builtin_is_pointer_inverconvertible_with_class have one bug, > for unions the pointer inter-convertibility doesn't talk about std layout at > all, so I think I need to check for std_layout_type_p only for non-union > class types and accept any union, std_layout_type_p or not. But when > recursing from a union type into anonymous structure type punt if the > anonymous structure type is not std_layout_type_p + add testcase coverage. > > Anyway, the patch has been bootstrapped/regtested on x86_64-linux and > i686-linux. > > 2021-07-29 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_1, > 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. > > --- gcc/c-family/c-common.h.jj 2021-07-21 18:55:05.239627937 +0200 > +++ gcc/c-family/c-common.h 2021-07-28 12:07:31.717700448 +0200 > @@ -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, > --- gcc/c-family/c-common.c.jj 2021-07-21 18:55:05.239627937 +0200 > +++ gcc/c-family/c-common.c 2021-07-28 12:08:26.419963865 +0200 > @@ -421,6 +421,8 @@ const struct c_common_resword c_common_r > { "__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 }, > --- gcc/cp/cp-tree.h.jj 2021-07-21 18:55:05.326626722 +0200 > +++ gcc/cp/cp-tree.h 2021-07-28 14:08:55.920562235 +0200 > @@ -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"))) tin > 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 > 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); > --- gcc/cp/parser.c.jj 2021-07-27 09:47:44.098780350 +0200 > +++ gcc/cp/parser.c 2021-07-28 12:10:24.669370137 +0200 > @@ -5797,6 +5797,7 @@ cp_parser_primary_expression (cp_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: > @@ -10686,6 +10687,10 @@ cp_parser_trait_expr (cp_parser* parser, > 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; > --- gcc/cp/cp-objcp-common.c.jj 2021-07-21 18:55:05.301627072 +0200 > +++ gcc/cp/cp-objcp-common.c 2021-07-28 12:10:40.358158672 +0200 > @@ -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: > --- gcc/cp/constraint.cc.jj 2021-07-21 18:55:05.281627351 +0200 > +++ gcc/cp/constraint.cc 2021-07-28 12:12:31.875655576 +0200 > @@ -3631,6 +3631,10 @@ diagnose_trait_expr (tree expr, tree arg > 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; > --- gcc/cp/decl.c.jj 2021-07-21 18:55:05.362626220 +0200 > +++ gcc/cp/decl.c 2021-07-28 13:35:55.640239697 +0200 > @@ -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. */ > --- gcc/cp/constexpr.c.jj 2021-07-21 18:55:05.256627699 +0200 > +++ gcc/cp/constexpr.c 2021-07-28 17:12:01.796428596 +0200 > @@ -1427,8 +1427,20 @@ cxx_eval_builtin_function_call (const co > && 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) > --- gcc/cp/semantics.c.jj 2021-07-22 12:37:20.450532703 +0200 > +++ gcc/cp/semantics.c 2021-07-28 16:51:05.119363473 +0200 > @@ -10566,6 +10566,135 @@ classtype_has_nothrow_assign_or_copy_p ( > return saw_copy; > } > > +/* Helper function for pointer_interconvertible_base_of_p. Verify > + that BINFO_TYPE (BINFO) is pointer interconvertible with BASE. */ > + > +static bool > +pointer_interconvertible_base_of_p_1 (tree binfo, tree base) > +{ > + for (tree field = TYPE_FIELDS (BINFO_TYPE (binfo)); > + field; field = DECL_CHAIN (field)) > + if (TREE_CODE (field) == FIELD_DECL && !DECL_FIELD_IS_BASE (field)) > + return false; I think checking non-static data members is a bug in the resolution of CWG 2254, which correctly changed 11.4 to say that the address of a standard-layout class is the same as the address of each base whether or not the class has non-static data members, but didn't change pointer-interconvertibility enough to match. I've raised this with CWG. I think we don't need this function at all. > + unsigned ix; > + tree base_binfo; > + for (ix = 0; BINFO_BASE_ITERATE (binfo, ix, base_binfo); ix++) > + if (SAME_BINFO_TYPE_P (BINFO_TYPE (base_binfo), base) > + || pointer_interconvertible_base_of_p_1 (base_binfo, base)) > + return true; > + return false; > +} > + > +/* 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 = cp_build_qualified_type (base, TYPE_UNQUALIFIED); > + derived = cp_build_qualified_type (derived, TYPE_UNQUALIFIED); These could be TYPE_MAIN_VARIANT. > + if (NON_UNION_CLASS_TYPE_P (base) > + && NON_UNION_CLASS_TYPE_P (derived) If either is not a non-union class, we should return false immediately. I don't know what the rationale was for returning false for arguments that are the same union, but that decision was made earlier, for std::is_base_of. > + && same_type_p (base, derived)) > + return true; > + > + if (!complete_type_or_else (derived, NULL_TREE)) > + return false; It's an error if derived is incomplete (again, like is_base_of). finish_trait_expr should have already handled that. > + if (!uniquely_derived_from_p (base, derived)) > + return false; > + > + if (!std_layout_type_p (derived)) > + return false; I might move this check above derived_from_p, as it's just checking a flag. > + return pointer_interconvertible_base_of_p_1 (TYPE_BINFO (derived), base); > +} > + > +/* 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 (type) != UNION_TYPE) > + return first_nonstatic_data_member_p (TREE_TYPE (field), > + membertype); > + if (first_nonstatic_data_member_p (TREE_TYPE (field), membertype)) > + return true; > + } > + else if (TREE_CODE (type) != UNION_TYPE) > + return same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (field), > + membertype); > + else if (same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (field), > + membertype)) > + return true; Instead of checking !UNION_TYPE twice above, you could check it once here and return false. More later. > + } > + 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 (!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 +10788,9 @@ trait_expr_value (cp_trait_kind kind, tr > 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 +10918,7 @@ finish_trait_expr (location_t loc, cp_tr > 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 +10936,9 @@ finish_trait_expr (location_t loc, cp_tr > 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, > --- gcc/cp/cp-gimplify.c.jj 2021-07-28 12:06:00.482928952 +0200 > +++ gcc/cp/cp-gimplify.c 2021-07-28 14:49:06.034027853 +0200 > @@ -648,14 +648,23 @@ cp_gimplify_expr (tree *expr_p, gimple_s > 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, > --- gcc/cp/tree.c.jj 2021-07-21 18:55:05.416625466 +0200 > +++ gcc/cp/tree.c 2021-07-28 14:07:41.181568566 +0200 > @@ -450,11 +450,16 @@ builtin_valid_in_constant_expr_p (const_ > 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; > } > --- gcc/cp/cxx-pretty-print.c.jj 2021-07-21 18:55:05.344626471 +0200 > +++ gcc/cp/cxx-pretty-print.c 2021-07-28 12:14:20.675189115 +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_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_prin > 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)); > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C.jj 2021-07-28 13:21:41.654721641 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C 2021-07-28 13:21:20.562005151 +0200 > @@ -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>); > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C.jj 2021-07-28 17:23:23.353247589 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C 2021-07-28 17:36:10.514918765 +0200 > @@ -0,0 +1,62 @@ > +// 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 () {} }; > + > +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)); > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C.jj 2021-07-28 17:31:11.981938196 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C 2021-07-28 17:39:45.458022541 +0200 > @@ -0,0 +1,128 @@ > +// 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 () {} }; > + > +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 (); > +} > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C.jj 2021-07-28 17:31:19.863832080 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C 2021-07-28 17:34:52.767965633 +0200 > @@ -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" } > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C.jj 2021-07-28 17:36:30.412650659 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C 2021-07-28 17:37:30.435841885 +0200 > @@ -0,0 +1,24 @@ > +// 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; }; > + > +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)); > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C.jj 2021-07-28 17:36:35.294584876 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C 2021-07-28 17:39:08.795516549 +0200 > @@ -0,0 +1,42 @@ > +// 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; }; > + > +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 (); > +} > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C.jj 2021-05-25 17:45:05.138850892 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C 2021-07-28 17:53:43.244733852 +0200 > @@ -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); > > Jakub >
On 7/29/21 4:38 PM, Jason Merrill wrote: > On 7/29/21 3:50 AM, Jakub Jelinek wrote: >> Hi! >> >> 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. >> >> 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 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. 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>); I think these are correct. >> 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>); I think these are wrong, given my comment below about CWG2254. >> 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? MSVC bug, I think. >> 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? > > I'll look into this. I think GCC/clang are correct here, I've raised this with CWG. >> Now that I'm writing the above text and rereading the >> pointer-interconvertibility definition, I think my >> first_nonstatic_data_member_p >> and fold_builtin_is_pointer_inverconvertible_with_class have one bug, >> for unions the pointer inter-convertibility doesn't talk about std >> layout at >> all, so I think I need to check for std_layout_type_p only for non-union >> class types and accept any union, std_layout_type_p or not. But when >> recursing from a union type into anonymous structure type punt if the >> anonymous structure type is not std_layout_type_p + add testcase >> coverage. >> >> Anyway, the patch has been bootstrapped/regtested on x86_64-linux and >> i686-linux. >> >> 2021-07-29 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_1, >> 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. >> >> --- gcc/c-family/c-common.h.jj 2021-07-21 18:55:05.239627937 +0200 >> +++ gcc/c-family/c-common.h 2021-07-28 12:07:31.717700448 +0200 >> @@ -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, >> --- gcc/c-family/c-common.c.jj 2021-07-21 18:55:05.239627937 +0200 >> +++ gcc/c-family/c-common.c 2021-07-28 12:08:26.419963865 +0200 >> @@ -421,6 +421,8 @@ const struct c_common_resword c_common_r >> { "__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 }, >> --- gcc/cp/cp-tree.h.jj 2021-07-21 18:55:05.326626722 +0200 >> +++ gcc/cp/cp-tree.h 2021-07-28 14:08:55.920562235 +0200 >> @@ -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"))) tin >> 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 >> 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); >> --- gcc/cp/parser.c.jj 2021-07-27 09:47:44.098780350 +0200 >> +++ gcc/cp/parser.c 2021-07-28 12:10:24.669370137 +0200 >> @@ -5797,6 +5797,7 @@ cp_parser_primary_expression (cp_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: >> @@ -10686,6 +10687,10 @@ cp_parser_trait_expr (cp_parser* parser, >> 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; >> --- gcc/cp/cp-objcp-common.c.jj 2021-07-21 18:55:05.301627072 +0200 >> +++ gcc/cp/cp-objcp-common.c 2021-07-28 12:10:40.358158672 +0200 >> @@ -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: >> --- gcc/cp/constraint.cc.jj 2021-07-21 18:55:05.281627351 +0200 >> +++ gcc/cp/constraint.cc 2021-07-28 12:12:31.875655576 +0200 >> @@ -3631,6 +3631,10 @@ diagnose_trait_expr (tree expr, tree arg >> 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; >> --- gcc/cp/decl.c.jj 2021-07-21 18:55:05.362626220 +0200 >> +++ gcc/cp/decl.c 2021-07-28 13:35:55.640239697 +0200 >> @@ -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. */ >> --- gcc/cp/constexpr.c.jj 2021-07-21 18:55:05.256627699 +0200 >> +++ gcc/cp/constexpr.c 2021-07-28 17:12:01.796428596 +0200 >> @@ -1427,8 +1427,20 @@ cxx_eval_builtin_function_call (const co >> && 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) >> --- gcc/cp/semantics.c.jj 2021-07-22 12:37:20.450532703 +0200 >> +++ gcc/cp/semantics.c 2021-07-28 16:51:05.119363473 +0200 >> @@ -10566,6 +10566,135 @@ classtype_has_nothrow_assign_or_copy_p ( >> return saw_copy; >> } >> +/* Helper function for pointer_interconvertible_base_of_p. Verify >> + that BINFO_TYPE (BINFO) is pointer interconvertible with BASE. */ >> + >> +static bool >> +pointer_interconvertible_base_of_p_1 (tree binfo, tree base) >> +{ >> + for (tree field = TYPE_FIELDS (BINFO_TYPE (binfo)); >> + field; field = DECL_CHAIN (field)) >> + if (TREE_CODE (field) == FIELD_DECL && !DECL_FIELD_IS_BASE (field)) >> + return false; > > I think checking non-static data members is a bug in the resolution of > CWG 2254, which correctly changed 11.4 to say that the address of a > standard-layout class is the same as the address of each base whether or > not the class has non-static data members, but didn't change > pointer-interconvertibility enough to match. I've raised this with CWG. > > I think we don't need this function at all. > >> + unsigned ix; >> + tree base_binfo; >> + for (ix = 0; BINFO_BASE_ITERATE (binfo, ix, base_binfo); ix++) >> + if (SAME_BINFO_TYPE_P (BINFO_TYPE (base_binfo), base) >> + || pointer_interconvertible_base_of_p_1 (base_binfo, base)) >> + return true; >> + return false; >> +} >> + >> +/* 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 = cp_build_qualified_type (base, TYPE_UNQUALIFIED); >> + derived = cp_build_qualified_type (derived, TYPE_UNQUALIFIED); > > These could be TYPE_MAIN_VARIANT. > >> + if (NON_UNION_CLASS_TYPE_P (base) >> + && NON_UNION_CLASS_TYPE_P (derived) > > If either is not a non-union class, we should return false immediately. > I don't know what the rationale was for returning false for arguments > that are the same union, but that decision was made earlier, for > std::is_base_of. > >> + && same_type_p (base, derived)) >> + return true; >> + >> + if (!complete_type_or_else (derived, NULL_TREE)) >> + return false; > > It's an error if derived is incomplete (again, like is_base_of). > finish_trait_expr should have already handled that. > >> + if (!uniquely_derived_from_p (base, derived)) >> + return false; >> + >> + if (!std_layout_type_p (derived)) >> + return false; > > I might move this check above derived_from_p, as it's just checking a flag. > >> + return pointer_interconvertible_base_of_p_1 (TYPE_BINFO (derived), >> base); >> +} >> + >> +/* 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 (type) != UNION_TYPE) >> + return first_nonstatic_data_member_p (TREE_TYPE (field), >> + membertype); >> + if (first_nonstatic_data_member_p (TREE_TYPE (field), membertype)) >> + return true; >> + } >> + else if (TREE_CODE (type) != UNION_TYPE) >> + return same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (field), >> + membertype); >> + else if (same_type_ignoring_top_level_qualifiers_p (TREE_TYPE >> (field), >> + membertype)) >> + return true; > > Instead of checking !UNION_TYPE twice above, you could check it once > here and return false. > > More later. > >> + } >> + 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 (!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 +10788,9 @@ trait_expr_value (cp_trait_kind kind, tr >> 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 +10918,7 @@ finish_trait_expr (location_t loc, cp_tr >> 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 +10936,9 @@ finish_trait_expr (location_t loc, cp_tr >> 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, >> --- gcc/cp/cp-gimplify.c.jj 2021-07-28 12:06:00.482928952 +0200 >> +++ gcc/cp/cp-gimplify.c 2021-07-28 14:49:06.034027853 +0200 >> @@ -648,14 +648,23 @@ cp_gimplify_expr (tree *expr_p, gimple_s >> 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, >> --- gcc/cp/tree.c.jj 2021-07-21 18:55:05.416625466 +0200 >> +++ gcc/cp/tree.c 2021-07-28 14:07:41.181568566 +0200 >> @@ -450,11 +450,16 @@ builtin_valid_in_constant_expr_p (const_ >> 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; >> } >> --- gcc/cp/cxx-pretty-print.c.jj 2021-07-21 18:55:05.344626471 +0200 >> +++ gcc/cp/cxx-pretty-print.c 2021-07-28 12:14:20.675189115 +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_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_prin >> 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)); >> --- >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C.jj 2021-07-28 >> 13:21:41.654721641 +0200 >> +++ >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C >> 2021-07-28 13:21:20.562005151 +0200 >> @@ -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>); >> --- >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C.jj >> 2021-07-28 17:23:23.353247589 +0200 >> +++ >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C 2021-07-28 >> 17:36:10.514918765 +0200 >> @@ -0,0 +1,62 @@ >> +// 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 () {} }; >> + >> +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)); >> --- >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C.jj >> 2021-07-28 17:31:11.981938196 +0200 >> +++ >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C 2021-07-28 >> 17:39:45.458022541 +0200 >> @@ -0,0 +1,128 @@ >> +// 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 () {} }; >> + >> +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 (); >> +} >> --- >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C.jj >> 2021-07-28 17:31:19.863832080 +0200 >> +++ >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C 2021-07-28 >> 17:34:52.767965633 +0200 >> @@ -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" } >> --- >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C.jj >> 2021-07-28 17:36:30.412650659 +0200 >> +++ >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C 2021-07-28 >> 17:37:30.435841885 +0200 >> @@ -0,0 +1,24 @@ >> +// 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; }; >> + >> +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)); >> --- >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C.jj >> 2021-07-28 17:36:35.294584876 +0200 >> +++ >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C 2021-07-28 >> 17:39:08.795516549 +0200 >> @@ -0,0 +1,42 @@ >> +// 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; }; >> + >> +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 (); >> +} >> --- >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C.jj >> 2021-05-25 17:45:05.138850892 +0200 >> +++ >> gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C 2021-07-28 >> 17:53:43.244733852 +0200 >> @@ -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); >> >> Jakub >> >
On Thu, Jul 29, 2021 at 04:38:44PM -0400, Jason Merrill wrote: > > +/* Helper function for pointer_interconvertible_base_of_p. Verify > > + that BINFO_TYPE (BINFO) is pointer interconvertible with BASE. */ > > + > > +static bool > > +pointer_interconvertible_base_of_p_1 (tree binfo, tree base) > > +{ > > + for (tree field = TYPE_FIELDS (BINFO_TYPE (binfo)); > > + field; field = DECL_CHAIN (field)) > > + if (TREE_CODE (field) == FIELD_DECL && !DECL_FIELD_IS_BASE (field)) > > + return false; > > I think checking non-static data members is a bug in the resolution of CWG > 2254, which correctly changed 11.4 to say that the address of a > standard-layout class is the same as the address of each base whether or not > the class has non-static data members, but didn't change > pointer-interconvertibility enough to match. I've raised this with CWG. > > I think we don't need this function at all. Ok. ... > Instead of checking !UNION_TYPE twice above, you could check it once here > and return false. Here is an updated patch, which includes the incremental patch for non-std-layout unions (with no changes for non-stdlayout in anon structure in union though) and has your review comments above incorporated. All the changes from the combination of the original and incremental patch are in gcc/cp/semantics.c and gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C. 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. --- gcc/c-family/c-common.h.jj 2021-07-29 13:24:08.368481637 +0200 +++ gcc/c-family/c-common.h 2021-07-30 11:19:39.391615252 +0200 @@ -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, --- gcc/c-family/c-common.c.jj 2021-07-29 13:24:42.667013302 +0200 +++ gcc/c-family/c-common.c 2021-07-30 11:19:39.411614978 +0200 @@ -421,6 +421,8 @@ const struct c_common_resword c_common_r { "__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 }, --- gcc/cp/cp-tree.h.jj 2021-07-29 13:24:08.673477473 +0200 +++ gcc/cp/cp-tree.h 2021-07-30 11:19:39.426614772 +0200 @@ -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"))) tin 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 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); --- gcc/cp/parser.c.jj 2021-07-30 10:30:07.115413053 +0200 +++ gcc/cp/parser.c 2021-07-30 11:19:39.431614703 +0200 @@ -5799,6 +5799,7 @@ cp_parser_primary_expression (cp_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, 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; --- gcc/cp/cp-objcp-common.c.jj 2021-07-29 13:24:08.599478483 +0200 +++ gcc/cp/cp-objcp-common.c 2021-07-30 11:19:39.440614580 +0200 @@ -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: --- gcc/cp/constraint.cc.jj 2021-07-29 13:24:08.527479466 +0200 +++ gcc/cp/constraint.cc 2021-07-30 11:19:39.455614374 +0200 @@ -3631,6 +3631,10 @@ diagnose_trait_expr (tree expr, tree arg 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; --- gcc/cp/decl.c.jj 2021-07-29 13:24:08.797475780 +0200 +++ gcc/cp/decl.c 2021-07-30 11:19:39.476614087 +0200 @@ -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. */ --- gcc/cp/constexpr.c.jj 2021-07-29 13:24:08.447480558 +0200 +++ gcc/cp/constexpr.c 2021-07-30 11:19:39.493613854 +0200 @@ -1427,8 +1427,20 @@ cxx_eval_builtin_function_call (const co && 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) --- gcc/cp/semantics.c.jj 2021-07-29 13:24:08.960473554 +0200 +++ gcc/cp/semantics.c 2021-07-30 11:27:16.247352685 +0200 @@ -10566,6 +10566,111 @@ classtype_has_nothrow_assign_or_copy_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 (type) != UNION_TYPE + || 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 +10764,9 @@ trait_expr_value (cp_trait_kind kind, tr 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 +10894,7 @@ finish_trait_expr (location_t loc, cp_tr 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 +10912,9 @@ finish_trait_expr (location_t loc, cp_tr 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, --- gcc/cp/cp-gimplify.c.jj 2021-07-29 13:24:08.599478483 +0200 +++ gcc/cp/cp-gimplify.c 2021-07-30 11:19:39.525613415 +0200 @@ -648,14 +648,23 @@ cp_gimplify_expr (tree *expr_p, gimple_s 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, --- gcc/cp/tree.c.jj 2021-07-29 13:24:09.017472775 +0200 +++ gcc/cp/tree.c 2021-07-30 11:19:39.539613223 +0200 @@ -450,11 +450,16 @@ builtin_valid_in_constant_expr_p (const_ 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; } --- gcc/cp/cxx-pretty-print.c.jj 2021-07-29 13:24:08.734476640 +0200 +++ gcc/cp/cxx-pretty-print.c 2021-07-30 11:19:39.553613031 +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_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_prin 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)); --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C.jj 2021-07-30 11:19:39.553613031 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C 2021-07-30 11:39:51.666998275 +0200 @@ -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>); --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C.jj 2021-07-30 11:19:39.553613031 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C 2021-07-30 11:19:52.473435926 +0200 @@ -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)); --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C.jj 2021-07-30 11:19:39.553613031 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C 2021-07-30 11:19:52.473435926 +0200 @@ -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 (); +} --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C.jj 2021-07-30 11:19:39.553613031 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C 2021-07-30 11:19:39.553613031 +0200 @@ -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" } --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C.jj 2021-07-30 11:19:39.553613031 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C 2021-07-30 11:19:52.473435926 +0200 @@ -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 {}; +struct E { [[no_unique_address]] D e; }; +union Y { int a; struct : public E { short b; long c; }; 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::d)); --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C.jj 2021-07-30 11:19:39.553613031 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C 2021-07-30 11:19:52.473435926 +0200 @@ -0,0 +1,57 @@ +// 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 {}; +struct E { [[no_unique_address]] D e; }; +union Y { int a; struct : public E { short b; long c; }; 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::d; + if (!std::is_pointer_interconvertible_with_class (t11)) + __builtin_abort (); +} --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C.jj 2021-07-30 11:19:39.553613031 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C 2021-07-30 11:19:39.553613031 +0200 @@ -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); Jakub
On 7/30/21 5:51 AM, Jakub Jelinek wrote: > On Thu, Jul 29, 2021 at 04:38:44PM -0400, Jason Merrill wrote: >> We don't already reject an anonymous struct with bases? I think we should >> do so, in fixup_anonymous_aggr. We might even require anonymous structs to >> be standard-layout. > > Not having base classes seems reasonable requirement for the anonymous > structures, after all, I couldn't find a way to refer to the members > in the base class - &Y::e is rejected with the above. Patch attached. > But standard layout means that even all the non-static members of the struct > need to be standard-layout, that seems an unnecessary requirement for > anon structures to me. Good point. But then, if the anonymous struct is non-standard-layout, that should make the enclosing class non-standard-layout as well, so we should never need to consider in the pointer-interconv code whether the anonymous struct is standard-layout. >>> +/* Helper function for pointer_interconvertible_base_of_p. Verify >>> + that BINFO_TYPE (BINFO) is pointer interconvertible with BASE. */ >>> + >>> +static bool >>> +pointer_interconvertible_base_of_p_1 (tree binfo, tree base) >>> +{ >>> + for (tree field = TYPE_FIELDS (BINFO_TYPE (binfo)); >>> + field; field = DECL_CHAIN (field)) >>> + if (TREE_CODE (field) == FIELD_DECL && !DECL_FIELD_IS_BASE (field)) >>> + return false; >> >> I think checking non-static data members is a bug in the resolution of CWG >> 2254, which correctly changed 11.4 to say that the address of a >> standard-layout class is the same as the address of each base whether or not >> the class has non-static data members, but didn't change >> pointer-interconvertibility enough to match. I've raised this with CWG. >> >> I think we don't need this function at all. > > Ok. > > ... >> Instead of checking !UNION_TYPE twice above, you could check it once here >> and return false. > > Here is an updated patch, which includes the incremental patch for > non-std-layout unions (with no changes for non-stdlayout in anon structure > in union though) and has your review comments above incorporated. > All the changes from the combination of the original and incremental patch > are in gcc/cp/semantics.c and > gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C. > > 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. > > --- gcc/c-family/c-common.h.jj 2021-07-29 13:24:08.368481637 +0200 > +++ gcc/c-family/c-common.h 2021-07-30 11:19:39.391615252 +0200 > @@ -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, > --- gcc/c-family/c-common.c.jj 2021-07-29 13:24:42.667013302 +0200 > +++ gcc/c-family/c-common.c 2021-07-30 11:19:39.411614978 +0200 > @@ -421,6 +421,8 @@ const struct c_common_resword c_common_r > { "__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 }, > --- gcc/cp/cp-tree.h.jj 2021-07-29 13:24:08.673477473 +0200 > +++ gcc/cp/cp-tree.h 2021-07-30 11:19:39.426614772 +0200 > @@ -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"))) tin > 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 > 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); > --- gcc/cp/parser.c.jj 2021-07-30 10:30:07.115413053 +0200 > +++ gcc/cp/parser.c 2021-07-30 11:19:39.431614703 +0200 > @@ -5799,6 +5799,7 @@ cp_parser_primary_expression (cp_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, > 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; > --- gcc/cp/cp-objcp-common.c.jj 2021-07-29 13:24:08.599478483 +0200 > +++ gcc/cp/cp-objcp-common.c 2021-07-30 11:19:39.440614580 +0200 > @@ -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: > --- gcc/cp/constraint.cc.jj 2021-07-29 13:24:08.527479466 +0200 > +++ gcc/cp/constraint.cc 2021-07-30 11:19:39.455614374 +0200 > @@ -3631,6 +3631,10 @@ diagnose_trait_expr (tree expr, tree arg > 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; > --- gcc/cp/decl.c.jj 2021-07-29 13:24:08.797475780 +0200 > +++ gcc/cp/decl.c 2021-07-30 11:19:39.476614087 +0200 > @@ -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. */ > --- gcc/cp/constexpr.c.jj 2021-07-29 13:24:08.447480558 +0200 > +++ gcc/cp/constexpr.c 2021-07-30 11:19:39.493613854 +0200 > @@ -1427,8 +1427,20 @@ cxx_eval_builtin_function_call (const co > && 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) > --- gcc/cp/semantics.c.jj 2021-07-29 13:24:08.960473554 +0200 > +++ gcc/cp/semantics.c 2021-07-30 11:27:16.247352685 +0200 > @@ -10566,6 +10566,111 @@ classtype_has_nothrow_assign_or_copy_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 (type) != UNION_TYPE > + || 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; Here I was thinking just if (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 +10764,9 @@ trait_expr_value (cp_trait_kind kind, tr > 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 +10894,7 @@ finish_trait_expr (location_t loc, cp_tr > 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 +10912,9 @@ finish_trait_expr (location_t loc, cp_tr > 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, > --- gcc/cp/cp-gimplify.c.jj 2021-07-29 13:24:08.599478483 +0200 > +++ gcc/cp/cp-gimplify.c 2021-07-30 11:19:39.525613415 +0200 > @@ -648,14 +648,23 @@ cp_gimplify_expr (tree *expr_p, gimple_s > 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, > --- gcc/cp/tree.c.jj 2021-07-29 13:24:09.017472775 +0200 > +++ gcc/cp/tree.c 2021-07-30 11:19:39.539613223 +0200 > @@ -450,11 +450,16 @@ builtin_valid_in_constant_expr_p (const_ > 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; > } > --- gcc/cp/cxx-pretty-print.c.jj 2021-07-29 13:24:08.734476640 +0200 > +++ gcc/cp/cxx-pretty-print.c 2021-07-30 11:19:39.553613031 +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_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_prin > 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)); > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C.jj 2021-07-30 11:19:39.553613031 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C 2021-07-30 11:39:51.666998275 +0200 > @@ -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>); > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C.jj 2021-07-30 11:19:39.553613031 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C 2021-07-30 11:19:52.473435926 +0200 > @@ -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)); > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C.jj 2021-07-30 11:19:39.553613031 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C 2021-07-30 11:19:52.473435926 +0200 > @@ -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 (); > +} > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C.jj 2021-07-30 11:19:39.553613031 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C 2021-07-30 11:19:39.553613031 +0200 > @@ -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" } > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C.jj 2021-07-30 11:19:39.553613031 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C 2021-07-30 11:19:52.473435926 +0200 > @@ -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 {}; > +struct E { [[no_unique_address]] D e; }; > +union Y { int a; struct : public E { short b; long c; }; 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::d)); > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C.jj 2021-07-30 11:19:39.553613031 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C 2021-07-30 11:19:52.473435926 +0200 > @@ -0,0 +1,57 @@ > +// 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 {}; > +struct E { [[no_unique_address]] D e; }; > +union Y { int a; struct : public E { short b; long c; }; 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::d; > + if (!std::is_pointer_interconvertible_with_class (t11)) > + __builtin_abort (); > +} > --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C.jj 2021-07-30 11:19:39.553613031 +0200 > +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C 2021-07-30 11:19:39.553613031 +0200 > @@ -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); > > > Jakub >
On Fri, Jul 30, 2021 at 11:00:26AM -0400, Jason Merrill wrote: > Patch attached. LGTM (which would mean I'll need to replace that particular test union with a different one which will have just a non-std-layout member of the anon struct, see below), but I guess we want a testcase for this, e.g. struct E { }; struct X { int a; struct : public E { short b; long c; }; long long d; }; union Y { int a; struct : public E { short b; long c; }; long long d; }; will do it. > > But standard layout means that even all the non-static members of the struct > > need to be standard-layout, that seems an unnecessary requirement for > > anon structures to me. > > Good point. > > But then, if the anonymous struct is non-standard-layout, that should make > the enclosing class non-standard-layout as well, so we should never need to > consider in the pointer-interconv code whether the anonymous struct is > standard-layout. For non-std-layout anon struct in a non-union class sure. But, for non-std-layout anon struct in a union, while it makes the union also non-std-layout, pointer-interconvertibility doesn't care about std-layout, even in non-std-layout unions address of each of the union member is pointer-interconvertible with the address of the whole union object. But when we recurse into the anon struct, the rule for non-union classes applies and it only handles std-layout. So something like: 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 (&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::d)); D is not std-layout, therefore the anon-struct is not std-layout either and neither is union Y, for &Y::a and &Y::d the union pointer-interconvertibility rule applies and both are pointer-interconvertible, for the anon-struct it is not std-layout and therefore address of the artificial member with anon-struct type is not pointer-interconvertible with address of the b member. Jakub
On 7/30/21 11:23 AM, Jakub Jelinek wrote: > On Fri, Jul 30, 2021 at 11:00:26AM -0400, Jason Merrill wrote: >> Patch attached. > > LGTM (which would mean I'll need to replace that particular test union > with a different one which will have just a non-std-layout member of the > anon struct, see below), but I guess we want a testcase for this, e.g. > struct E { }; > struct X { int a; struct : public E { short b; long c; }; long long d; }; > union Y { int a; struct : public E { short b; long c; }; long long d; }; > will do it. I've now committed this change. >>> But standard layout means that even all the non-static members of the struct >>> need to be standard-layout, that seems an unnecessary requirement for >>> anon structures to me. >> >> Good point. >> >> But then, if the anonymous struct is non-standard-layout, that should make >> the enclosing class non-standard-layout as well, so we should never need to >> consider in the pointer-interconv code whether the anonymous struct is >> standard-layout. > > For non-std-layout anon struct in a non-union class sure. > But, for non-std-layout anon struct in a union, while it makes the union > also non-std-layout, pointer-interconvertibility doesn't care about > std-layout, even in non-std-layout unions address of each of the union member > is pointer-interconvertible with the address of the whole union object. Ah, right, I remembered that yesterday, but forgot this morning... So the handling in your revised patch is good; let's not worry about the case you mentioned with an anonymous struct member of the same type as another union member. Though > + if ((TREE_CODE (type) != UNION_TYPE This line could use either a comment or to be dropped; it's true that if type is non-union, we can skip the other checks because we know that any anonymous struct member must be standard-layout, but the other checks are cheap enough that I'm inclined to omit this one. OK either way and with the testcase adjustments for my patch. > + || TREE_CODE (TREE_TYPE (field)) == UNION_TYPE > || std_layout_type_p (TREE_TYPE (field))) > && first_nonstatic_data_member_p (TREE_TYPE (field), membertype))
--- gcc/c-family/c-common.h.jj 2021-07-21 18:55:05.239627937 +0200 +++ gcc/c-family/c-common.h 2021-07-28 12:07:31.717700448 +0200 @@ -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, --- gcc/c-family/c-common.c.jj 2021-07-21 18:55:05.239627937 +0200 +++ gcc/c-family/c-common.c 2021-07-28 12:08:26.419963865 +0200 @@ -421,6 +421,8 @@ const struct c_common_resword c_common_r { "__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 }, --- gcc/cp/cp-tree.h.jj 2021-07-21 18:55:05.326626722 +0200 +++ gcc/cp/cp-tree.h 2021-07-28 14:08:55.920562235 +0200 @@ -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"))) tin 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 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); --- gcc/cp/parser.c.jj 2021-07-27 09:47:44.098780350 +0200 +++ gcc/cp/parser.c 2021-07-28 12:10:24.669370137 +0200 @@ -5797,6 +5797,7 @@ cp_parser_primary_expression (cp_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: @@ -10686,6 +10687,10 @@ cp_parser_trait_expr (cp_parser* parser, 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; --- gcc/cp/cp-objcp-common.c.jj 2021-07-21 18:55:05.301627072 +0200 +++ gcc/cp/cp-objcp-common.c 2021-07-28 12:10:40.358158672 +0200 @@ -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: --- gcc/cp/constraint.cc.jj 2021-07-21 18:55:05.281627351 +0200 +++ gcc/cp/constraint.cc 2021-07-28 12:12:31.875655576 +0200 @@ -3631,6 +3631,10 @@ diagnose_trait_expr (tree expr, tree arg 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; --- gcc/cp/decl.c.jj 2021-07-21 18:55:05.362626220 +0200 +++ gcc/cp/decl.c 2021-07-28 13:35:55.640239697 +0200 @@ -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. */ --- gcc/cp/constexpr.c.jj 2021-07-21 18:55:05.256627699 +0200 +++ gcc/cp/constexpr.c 2021-07-28 17:12:01.796428596 +0200 @@ -1427,8 +1427,20 @@ cxx_eval_builtin_function_call (const co && 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) --- gcc/cp/semantics.c.jj 2021-07-22 12:37:20.450532703 +0200 +++ gcc/cp/semantics.c 2021-07-28 16:51:05.119363473 +0200 @@ -10566,6 +10566,135 @@ classtype_has_nothrow_assign_or_copy_p ( return saw_copy; } +/* Helper function for pointer_interconvertible_base_of_p. Verify + that BINFO_TYPE (BINFO) is pointer interconvertible with BASE. */ + +static bool +pointer_interconvertible_base_of_p_1 (tree binfo, tree base) +{ + for (tree field = TYPE_FIELDS (BINFO_TYPE (binfo)); + field; field = DECL_CHAIN (field)) + if (TREE_CODE (field) == FIELD_DECL && !DECL_FIELD_IS_BASE (field)) + return false; + + unsigned ix; + tree base_binfo; + for (ix = 0; BINFO_BASE_ITERATE (binfo, ix, base_binfo); ix++) + if (SAME_BINFO_TYPE_P (BINFO_TYPE (base_binfo), base) + || pointer_interconvertible_base_of_p_1 (base_binfo, base)) + return true; + return false; +} + +/* 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 = cp_build_qualified_type (base, TYPE_UNQUALIFIED); + derived = cp_build_qualified_type (derived, TYPE_UNQUALIFIED); + if (NON_UNION_CLASS_TYPE_P (base) + && NON_UNION_CLASS_TYPE_P (derived) + && same_type_p (base, derived)) + return true; + + if (!complete_type_or_else (derived, NULL_TREE)) + return false; + + if (!uniquely_derived_from_p (base, derived)) + return false; + + if (!std_layout_type_p (derived)) + return false; + + return pointer_interconvertible_base_of_p_1 (TYPE_BINFO (derived), base); +} + +/* 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 (type) != UNION_TYPE) + return first_nonstatic_data_member_p (TREE_TYPE (field), + membertype); + if (first_nonstatic_data_member_p (TREE_TYPE (field), membertype)) + return true; + } + else if (TREE_CODE (type) != UNION_TYPE) + return same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (field), + membertype); + else if (same_type_ignoring_top_level_qualifiers_p (TREE_TYPE (field), + membertype)) + return true; + } + 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 (!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 +10788,9 @@ trait_expr_value (cp_trait_kind kind, tr 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 +10918,7 @@ finish_trait_expr (location_t loc, cp_tr 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 +10936,9 @@ finish_trait_expr (location_t loc, cp_tr 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, --- gcc/cp/cp-gimplify.c.jj 2021-07-28 12:06:00.482928952 +0200 +++ gcc/cp/cp-gimplify.c 2021-07-28 14:49:06.034027853 +0200 @@ -648,14 +648,23 @@ cp_gimplify_expr (tree *expr_p, gimple_s 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, --- gcc/cp/tree.c.jj 2021-07-21 18:55:05.416625466 +0200 +++ gcc/cp/tree.c 2021-07-28 14:07:41.181568566 +0200 @@ -450,11 +450,16 @@ builtin_valid_in_constant_expr_p (const_ 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; } --- gcc/cp/cxx-pretty-print.c.jj 2021-07-21 18:55:05.344626471 +0200 +++ gcc/cp/cxx-pretty-print.c 2021-07-28 12:14:20.675189115 +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_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_prin 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)); --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C.jj 2021-07-28 13:21:41.654721641 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-base-of1.C 2021-07-28 13:21:20.562005151 +0200 @@ -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>); --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C.jj 2021-07-28 17:23:23.353247589 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class1.C 2021-07-28 17:36:10.514918765 +0200 @@ -0,0 +1,62 @@ +// 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 () {} }; + +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)); --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C.jj 2021-07-28 17:31:11.981938196 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class2.C 2021-07-28 17:39:45.458022541 +0200 @@ -0,0 +1,128 @@ +// 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 () {} }; + +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 (); +} --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C.jj 2021-07-28 17:31:19.863832080 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class3.C 2021-07-28 17:34:52.767965633 +0200 @@ -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" } --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C.jj 2021-07-28 17:36:30.412650659 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class4.C 2021-07-28 17:37:30.435841885 +0200 @@ -0,0 +1,24 @@ +// 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; }; + +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)); --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C.jj 2021-07-28 17:36:35.294584876 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class5.C 2021-07-28 17:39:08.795516549 +0200 @@ -0,0 +1,42 @@ +// 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; }; + +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 (); +} --- gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C.jj 2021-05-25 17:45:05.138850892 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-pointer-interconvertible-with-class6.C 2021-07-28 17:53:43.244733852 +0200 @@ -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);