From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [216.205.24.124]) by sourceware.org (Postfix) with ESMTP id B075B3858400 for ; Fri, 30 Jul 2021 05:41:23 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org B075B3858400 Received: from mail-qv1-f70.google.com (mail-qv1-f70.google.com [209.85.219.70]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-261-s7ZYgKDZPPe-KdC1tpGLEg-1; Fri, 30 Jul 2021 01:41:18 -0400 X-MC-Unique: s7ZYgKDZPPe-KdC1tpGLEg-1 Received: by mail-qv1-f70.google.com with SMTP id kc5-20020a0562144105b0290334628b1005so4492839qvb.16 for ; Thu, 29 Jul 2021 22:41:17 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:subject:from:to:cc:references:message-id:date :user-agent:mime-version:in-reply-to:content-language :content-transfer-encoding; bh=Cv5BkbWr0iLpHzfzeMhgWKdfe/hU3VtTy2NCUhfr+Mw=; b=aRg6ljMfMioQImzit/w7GycYh29O02T+woP2rZZ9QwMuA+jkr0Hl+IGjnArL2zLCN7 p7dnXntsHO+bfGUTl5xrNvydvnfwCwlUDAhX/lGOSyvNy/aGxHbM/U6C5YdnbVu6z34z 0ERkSZT7uHNG/kZ72HqVLZMA95U0TRBFoT8a4x1+10RCRZBY3xhUxlpGFr56nHYoMDIM uFJMEronACCGqgcM71727QCQsSOo4mo/+TlHTZwbjjcaNFjAS/iHnG1tolMaG+tKvevu Vebe8PRF6GHuHf1uN1j9zRKga7tLlUgxNnlFcvsc3X2+HU5daPzPlqFVtz0Ce5ocR5lt Kpiw== X-Gm-Message-State: AOAM531R7rpLa8BS1b10bggzPfBVXbIiTiZd0bgxwYJ8bsRqomK3LeJ8 HaHWfwGtJ1e2sYe220AmREMvmvZKX5LBiR7FQwX9g7pOXsLQB5HEIKZpobg5p9pOLXe3c07kkiA 7hXvKLKDgwo2cvzs6lSxQrpz0Tgrv9qYwAgaUCNdk06/Y4eC5Rs/stBoBqSm4DuyGWQ== X-Received: by 2002:a05:6214:3a4:: with SMTP id m4mr774306qvy.17.1627623677208; Thu, 29 Jul 2021 22:41:17 -0700 (PDT) X-Google-Smtp-Source: ABdhPJw4ee2DZBK6AXpNY/9+dbR1igYVJIiHub9Z+3bdB0TxVvZk7zLfPoZ9Ejc3G6w1KuqW0bviNA== X-Received: by 2002:a05:6214:3a4:: with SMTP id m4mr774286qvy.17.1627623676755; Thu, 29 Jul 2021 22:41:16 -0700 (PDT) Received: from [192.168.1.148] (130-44-159-43.s11817.c3-0.arl-cbr1.sbo-arl.ma.cable.rcncustomer.com. [130.44.159.43]) by smtp.gmail.com with ESMTPSA id 71sm211106qtc.97.2021.07.29.22.41.15 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Thu, 29 Jul 2021 22:41:15 -0700 (PDT) Subject: Re: [PATCH] c++: Implement P0466R5 __cpp_lib_is_pointer_interconvertible compiler helpers [PR101539] From: Jason Merrill To: Jakub Jelinek , Jonathan Wakely Cc: gcc-patches@gcc.gnu.org References: <20210729075010.GX2380545@tucnak> Message-ID: <64e9e35f-d050-d240-de9e-ea8fe097f601@redhat.com> Date: Fri, 30 Jul 2021 01:41:14 -0400 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.11.0 MIME-Version: 1.0 In-Reply-To: X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset=utf-8; format=flowed Content-Language: en-US Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-5.6 required=5.0 tests=BAYES_00, BODY_8BITS, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, KAM_ASCII_DIVIDERS, NICE_REPLY_A, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 30 Jul 2021 05:41:28 -0000 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); >> static_assert (std::is_pointer_interconvertible_base_of_v); I think these are correct. >> static_assert (!std::is_pointer_interconvertible_base_of_v); >> static_assert (!std::is_pointer_interconvertible_base_of_v); >> static_assert (std::is_pointer_interconvertible_base_of_v> 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::b)); >> static_assert (std::is_pointer_interconvertible_with_class >> (&I::g)); >> static_assert (std::is_pointer_interconvertible_with_class >> (&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::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(int B::*)’ >>     22 | static_assert( is_pointer_interconvertible_with_class( >> &C::b ) ); >>        | >> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~ >> /tmp/1.C:8:1: note: candidate: ‘template 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::b ) ); >>        | >> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~ >> the second int argument isn't deduced. >> >> This boils down to: >> template >> 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::b); >> bool c = foo (&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  >> >>     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 >> +struct integral_constant >> +{ >> +  static constexpr T value = v; >> +}; >> + >> +template >> +struct is_pointer_interconvertible_base_of; >> + >> +template >> +struct is_pointer_interconvertible_base_of >> +  : public integral_constant > __is_pointer_interconvertible_base_of (T, U)> >> +{ >> +}; >> + >> +template >> +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::value); >> +static_assert (std::is_pointer_interconvertible_base_of_v); >> +static_assert (std::is_pointer_interconvertible_base_of_v> volatile A>); >> +static_assert (std::is_pointer_interconvertible_base_of_v); >> +static_assert (std::is_pointer_interconvertible_base_of_v> volatile C>); >> +static_assert (!std::is_pointer_interconvertible_base_of_v); >> +static_assert (!std::is_pointer_interconvertible_base_of_v); >> +static_assert (std::is_pointer_interconvertible_base_of_v); >> +static_assert (std::is_pointer_interconvertible_base_of_v); >> +static_assert (std::is_pointer_interconvertible_base_of_v); >> +static_assert (!std::is_pointer_interconvertible_base_of_v> volatile G>); >> +static_assert (!std::is_pointer_interconvertible_base_of_v> volatile G>); >> +static_assert (!std::is_pointer_interconvertible_base_of_v); >> +static_assert (!std::is_pointer_interconvertible_base_of_v> const I>); >> +static_assert (std::is_pointer_interconvertible_base_of_v); >> +static_assert (std::is_pointer_interconvertible_base_of_v> I>); >> +static_assert (!std::is_pointer_interconvertible_base_of_v> J, const K>); >> +static_assert (!std::is_pointer_interconvertible_base_of_v); >> --- >> 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 >> +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::b)); >> +static_assert (std::is_pointer_interconvertible_with_class (&G::g)); >> +static_assert (std::is_pointer_interconvertible_with_class >> (&G::g)); >> +static_assert (std::is_pointer_interconvertible_with_class (&I::g)); >> +static_assert (std::is_pointer_interconvertible_with_class >> (&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::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 >> +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 >> +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 >> +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 >> +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 >> >