[PATCH v23 32/33] c++: Implement __is_invocable built-in trait

Jason Merrill jason@redhat.com
Mon Oct 23 21:23:22 GMT 2023


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?

Jason



More information about the Libstdc++ mailing list