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

Reply via email to