On Mon, Oct 23, 2023 at 2:23 PM Jason Merrill <ja...@redhat.com> wrote:
>
> On 10/20/23 17:37, Patrick Palka wrote:
> > On Fri, 20 Oct 2023, Patrick Palka wrote:
> >
> >> On Fri, 20 Oct 2023, Patrick Palka wrote:
> >>
> >>> On Fri, 20 Oct 2023, Ken Matsui wrote:
> >>>
> >>>> This patch implements built-in trait for std::is_invocable.
> >>>
> >>> Nice!  My email client unfortunately ate my first review attempt, so
> >>> apologies for my brevity this time around.
> >>>
> >>>> gcc/cp/ChangeLog:
> >>>>
> >>>>    * cp-trait.def: Define __is_invocable.
> >>>>    * constraint.cc (diagnose_trait_expr): Handle CPTK_IS_INVOCABLE.
> >>>>    * semantics.cc (trait_expr_value): Likewise.
> >>>>    (finish_trait_expr): Likewise.
> >>>>    (is_invocable_p): New function.
> >>>>    * method.h: New file to export build_trait_object in method.cc.
>
> Given how much larger semantics.cc is than method.cc, maybe let's put
> is_invocable_p in method.cc instead?  And in general declarations can go
> in cp-tree.h.
>
> >>>> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> >>>> index 7cccbae5287..cc2e400531a 100644
> >>>> --- a/gcc/cp/semantics.cc
> >>>> +++ b/gcc/cp/semantics.cc
> >>>> @@ -45,6 +45,10 @@ along with GCC; see the file COPYING3.  If not see
> >>>>   #include "gomp-constants.h"
> >>>>   #include "predict.h"
> >>>>   #include "memmodel.h"
> >>>> +#include "method.h"
> >>>> +
> >>>> +#include "print-tree.h"
> >>>> +#include "tree-pretty-print.h"
> >>>>
> >>>>   /* There routines provide a modular interface to perform many parsing
> >>>>      operations.  They may therefore be used during actual parsing, or
> >>>> @@ -11714,6 +11718,133 @@ classtype_has_nothrow_assign_or_copy_p (tree 
> >>>> type, bool assign_p)
> >>>>     return saw_copy;
> >>>>   }
> >>>>
> >>>> +/* Return true if FN_TYPE is invocable with the given ARG_TYPES.  */
> >>>> +
> >>>> +static bool
> >>>> +is_invocable_p (tree fn_type, tree arg_types)
> >
> > (Sorry for the spam)  We'll eventually want to implement a built-in for
> > invoke_result, so perhaps we should preemptively factor out the bulk
> > of this function into a 'build_INVOKE' helper function that returns the
> > built tree?
> >
> >>>> +{
> >>>> +  /* ARG_TYPES must be a TREE_VEC.  */
> >>>> +  gcc_assert (TREE_CODE (arg_types) == TREE_VEC);
> >>>> +
> >>>> +  /* Access check is required to determine if the given is invocable.  
> >>>> */
> >>>> +  deferring_access_check_sentinel acs (dk_no_deferred);
> >>>> +
> >>>> +  /* std::is_invocable is an unevaluated context.  */
> >>>> +  cp_unevaluated cp_uneval_guard;
> >>>> +
> >>>> +  bool is_ptrdatamem;
> >>>> +  bool is_ptrmemfunc;
> >>>> +  if (TREE_CODE (fn_type) == REFERENCE_TYPE)
> >>>> +    {
> >>>> +      tree deref_fn_type = TREE_TYPE (fn_type);
> >>>> +      is_ptrdatamem = TYPE_PTRDATAMEM_P (deref_fn_type);
> >>>> +      is_ptrmemfunc = TYPE_PTRMEMFUNC_P (deref_fn_type);
> >>>> +
> >>>> +      /* Dereference fn_type if it is a pointer to member.  */
> >>>> +      if (is_ptrdatamem || is_ptrmemfunc)
> >>>> +  fn_type = deref_fn_type;
> >>>> +    }
> >>>> +  else
> >>>> +    {
> >>>> +      is_ptrdatamem = TYPE_PTRDATAMEM_P (fn_type);
> >>>> +      is_ptrmemfunc = TYPE_PTRMEMFUNC_P (fn_type);
> >>>> +    }
> >>>> +
> >>>> +  if (is_ptrdatamem && TREE_VEC_LENGTH (arg_types) != 1)
> >>>> +    /* A pointer to data member with non-one argument is not invocable. 
> >>>>  */
> >>>> +    return false;
> >>>> +
> >>>> +  if (is_ptrmemfunc && TREE_VEC_LENGTH (arg_types) == 0)
> >>>> +    /* A pointer to member function with no arguments is not invocable. 
> >>>>  */
> >>>> +    return false;
> >>>> +
> >>>> +  /* Construct an expression of a pointer to member.  */
> >>>> +  tree datum;
> >>>> +  if (is_ptrdatamem || is_ptrmemfunc)
> >>>> +    {
> >>>> +      tree datum_type = TREE_VEC_ELT (arg_types, 0);
> >>>> +
> >>>> +      /* Dereference datum.  */
> >>>> +      if (CLASS_TYPE_P (datum_type))
> >>>> +  {
> >>>> +    bool is_refwrap = false;
> >>>> +
> >>>> +    tree datum_decl = TYPE_NAME (TYPE_MAIN_VARIANT (datum_type));
> >>>> +    if (decl_in_std_namespace_p (datum_decl))
> >>>> +      {
> >>>> +        tree name = DECL_NAME (datum_decl);
> >>>> +        if (name && (id_equal (name, "reference_wrapper")))
> >>>> +          {
> >>>> +            /* Handle std::reference_wrapper.  */
> >>>> +            is_refwrap = true;
> >>>> +            datum_type = cp_build_reference_type (datum_type, false);
>
> Why do you change datum_type from std::reference_wrapper<...> to
> std::reference_wrapper<...>&?
>
> >>>> +          }
> >>>> +      }
> >>>> +
> >>>> +    datum = build_trait_object (datum_type);
> >>>> +
> >>>> +    /* If datum_type was not std::reference_wrapper, check if it has
> >>>> +       operator*() overload.  If datum_type was std::reference_wrapper,
> >>>> +       avoid dereferencing the datum twice.  */
> >>>> +    if (!is_refwrap)
> >>>> +      if (get_class_binding (datum_type, get_identifier ("operator*")))
> >>>
> >>> We probably should use lookup_member instead of get_class_binding since
> >>> IIUC the latter doesn't look into bases:
> >>>
> >>>    struct A { int m; };
> >>>    struct B { A& operator*(): };
> >>>    struct C : B { };
> >>>    static_assert(std::is_invocable_v<int A::*, C>);
> >>>
> >>> However, I notice that the specification of INVOKE
> >>> (https://eel.is/c++draft/func.require#lib:INVOKE) doesn't mention name
> >>> lookup at all so it strikes me as suspicious that we'd perform name
> >>> lookup here.
>
> Agreed.  It seems that whether or not to build_x_indirect_ref should
> depend instead on whether f is a pointer to a member of decltype(t1) (as
> well as is_refwrap).
>
> >>>  I think this would misbehave for:
> >>>
> >>>    struct A { };
> >>>    struct B : A { A& operator*() = delete; };
> >>>    static_assert(std::is_invocable_v<int A::*, B>);
> >>>
> >>>    struct C : private A { A& operator*(); };
> >>>    static_assert(std::is_invocable_v<int A::*, C>);
> >>
> >> Oops, this static_assert is missing a !
> >>
> >>>
> >>> ultimately because we end up choosing the dereference form of INVOKE,
> >>> but according to 1.1/1.4 we should choose the non-dereference form?
> >>>
> >>>> +        /* Handle operator*().  */
> >>>> +        datum = build_x_indirect_ref (UNKNOWN_LOCATION, datum,
> >>>> +                                      RO_UNARY_STAR, NULL_TREE,
> >>>> +                                      tf_none);
> >>>> +  }
> >>>> +      else if (POINTER_TYPE_P (datum_type))
> >>>> +  datum = build_trait_object (TREE_TYPE (datum_type));
> >>>> +      else
> >>>> +  datum = build_trait_object (datum_type);
> >>>> +    }
> >>>> +
> >>>> +  /* Build a function expression.  */
> >>>> +  tree fn;
> >>>> +  if (is_ptrdatamem)
> >>>> +    fn = build_m_component_ref (datum, build_trait_object (fn_type), 
> >>>> tf_none);
> >>>
> >>> Maybe exit early for the is_ptrdatamem case here (and simplify the rest
> >>> of the function accordingly)?
> >>>
> >>>> +  else if (is_ptrmemfunc)
> >>>> +    fn = build_trait_object (TYPE_PTRMEMFUNC_FN_TYPE (fn_type));
>
> Why not use build_m_component_ref and build_offset_ref_call_from_tree
> like it would if you wrote (t1.*f)() directly?
>

Thank you so much for your review!  I will apply your suggestions.

> Jason
>

Reply via email to