c++: add __is_deducible trait [PR105841]
authorJason Merrill <jason@redhat.com>
Thu, 9 Feb 2023 20:51:51 +0000 (12:51 -0800)
committerJason Merrill <jason@redhat.com>
Thu, 9 Mar 2023 15:25:45 +0000 (10:25 -0500)
C++20 class template argument deduction for an alias template involves
adding a constraint that the template arguments for the alias template can
be deduced from the return type of the deduction guide for the underlying
class template.  In the standard, this is modeled as defining a class
template with a partial specialization, but it's much more efficient to
implement with a trait that directly tries to perform the deduction.

The first argument to the trait is a template rather than a type, so various
places needed to be adjusted to accommodate that.

PR c++/105841

gcc/ChangeLog:

* doc/extend.texi (Type Traits):: Document __is_deducible.

gcc/cp/ChangeLog:

* cp-trait.def (IS_DEDUCIBLE): New.
* cxx-pretty-print.cc (pp_cxx_trait): Handle non-type.
* parser.cc (cp_parser_trait): Likewise.
* tree.cc (cp_tree_equal): Likewise.
* pt.cc (tsubst_copy_and_build): Likewise.
(type_targs_deducible_from): New.
(alias_ctad_tweaks): Use it.
* semantics.cc (trait_expr_value): Handle CPTK_IS_DEDUCIBLE.
(finish_trait_expr): Likewise.
* constraint.cc (diagnose_trait_expr): Likewise.
* cp-tree.h (type_targs_deducible_from): Declare.

gcc/testsuite/ChangeLog:

* g++.dg/ext/is_deducible1.C: New test.

gcc/cp/constraint.cc
gcc/cp/cp-trait.def
gcc/cp/cp-tree.h
gcc/cp/cxx-pretty-print.cc
gcc/cp/parser.cc
gcc/cp/pt.cc
gcc/cp/semantics.cc
gcc/cp/tree.cc
gcc/doc/extend.texi
gcc/testsuite/g++.dg/ext/is_deducible1.C [new file with mode: 0644]

index 9374327008b2fe0dc95b98882383d8308d0344e6..a28c85178fe6fa77a45ba080ad0c89bc4eef2c0b 100644 (file)
@@ -3797,6 +3797,9 @@ diagnose_trait_expr (tree expr, tree args)
       inform (loc, "  %qT is not a reference that binds to a temporary "
              "object of type %qT (copy-initialization)", t1, t2);
       break;
+    case CPTK_IS_DEDUCIBLE:
+      inform (loc, "  %qD is not deducible from %qT", t1, t2);
+      break;
 #define DEFTRAIT_TYPE(CODE, NAME, ARITY) \
     case CPTK_##CODE:
 #include "cp-trait.def"
index 823899a26c5d91e0427929ceefdf4e4ef92fd11f..e43fb464f42b32a5b9d7315cc6a004aabfd1f43c 100644 (file)
@@ -84,6 +84,7 @@ DEFTRAIT_EXPR (IS_TRIVIALLY_COPYABLE, "__is_trivially_copyable", 1)
 DEFTRAIT_EXPR (IS_UNION, "__is_union", 1)
 DEFTRAIT_EXPR (REF_CONSTRUCTS_FROM_TEMPORARY, "__reference_constructs_from_temporary", 2)
 DEFTRAIT_EXPR (REF_CONVERTS_FROM_TEMPORARY, "__reference_converts_from_temporary", 2)
+DEFTRAIT_EXPR (IS_DEDUCIBLE, "__is_deducible", 2)
 
 DEFTRAIT_TYPE (REMOVE_CV, "__remove_cv", 1)
 DEFTRAIT_TYPE (REMOVE_REFERENCE, "__remove_reference", 1)
index fb21c064141922182b6cbd74007819d8677c7f98..dfc1c8457684f01417590324ee1e48eb7e47e703 100644 (file)
@@ -7361,6 +7361,7 @@ extern tree fn_type_unification                   (tree, tree, tree,
                                                 bool, bool);
 extern void mark_decl_instantiated             (tree, int);
 extern int more_specialized_fn                 (tree, tree, int);
+extern bool type_targs_deducible_from          (tree, tree);
 extern void do_decl_instantiation              (tree, tree);
 extern void do_type_instantiation              (tree, tree, tsubst_flags_t);
 extern bool always_instantiate_p               (tree);
index bea52a608f1e9db0b42c896ce9d026697153dda4..7f4556d0da2b2ac827d3777d08bdee94dc7632bd 100644 (file)
@@ -2626,7 +2626,10 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
     }
 
   pp_cxx_left_paren (pp);
-  pp->type_id (type1);
+  if (TYPE_P (type1))
+    pp->type_id (type1);
+  else
+    pp->expression (type1);
   if (type2)
     {
       if (TREE_CODE (type2) != TREE_LIST)
index b00a6cd5b8b33aa2ee9e2b4ae3a833668e926ec6..533041946c0967edddba75d4c97ae9c32069f5e8 100644 (file)
@@ -10960,10 +10960,22 @@ cp_parser_trait (cp_parser* parser, enum rid keyword)
   matching_parens parens;
   parens.require_open (parser);
 
-  {
-    type_id_in_expr_sentinel s (parser);
-    type1 = cp_parser_type_id (parser);
-  }
+  if (kind == CPTK_IS_DEDUCIBLE)
+    {
+      const cp_token* token = cp_lexer_peek_token (parser->lexer);
+      type1 = cp_parser_id_expression (parser,
+                                      /*template_keyword_p=*/false,
+                                      /*check_dependency_p=*/true,
+                                      nullptr,
+                                      /*declarator_p=*/false,
+                                      /*optional_p=*/false);
+      type1 = cp_parser_lookup_name_simple (parser, type1, token->location);
+    }
+  else
+    {
+      type_id_in_expr_sentinel s (parser);
+      type1 = cp_parser_type_id (parser);
+    }
 
   if (type1 == error_mark_node)
     return error_mark_node;
index aafc99d12c36daed855ea45a2942b75f3c308c49..e87cda245b29f93a4b41c10d9020977525acd0ab 100644 (file)
@@ -21587,8 +21587,8 @@ tsubst_copy_and_build (tree t,
 
     case TRAIT_EXPR:
       {
-       tree type1 = tsubst (TRAIT_EXPR_TYPE1 (t), args,
-                            complain, in_decl);
+       tree type1 = tsubst_copy (TRAIT_EXPR_TYPE1 (t), args,
+                                 complain, in_decl);
        tree type2 = tsubst (TRAIT_EXPR_TYPE2 (t), args,
                             complain, in_decl);
        RETURN (finish_trait_expr (TRAIT_EXPR_LOCATION (t),
@@ -29989,7 +29989,7 @@ alias_ctad_tweaks (tree tmpl, tree uguides)
   /* This implementation differs from the above in two significant ways:
 
      1) We include all template parameters of A, not just some.
-     2) The added constraint is same_type instead of deducible.
+     2) [fixed] The added constraint is same_type instead of deducible.
 
      I believe that while it's probably possible to construct a testcase that
      behaves differently with this simplification, it should have the same
@@ -30089,7 +30089,7 @@ alias_ctad_tweaks (tree tmpl, tree uguides)
              /* FIXME this should mean they don't compare as equivalent.  */
              || dependent_alias_template_spec_p (atype, nt_opaque))
            {
-             tree same = finish_trait_expr (loc, CPTK_IS_SAME, atype, ret);
+             tree same = finish_trait_expr (loc, CPTK_IS_DEDUCIBLE, tmpl, ret);
              ci = append_constraint (ci, same);
            }
 
@@ -30103,12 +30103,7 @@ alias_ctad_tweaks (tree tmpl, tree uguides)
        {
          /* For a non-template deduction guide, if the arguments of A aren't
             deducible from the return type, don't add the candidate.  */
-         tree targs = make_tree_vec (natparms);
-         int err = unify (atparms, targs, utype, ret, UNIFY_ALLOW_NONE, false);
-         for (unsigned i = 0; !err && i < natparms; ++i)
-           if (TREE_VEC_ELT (targs, i) == NULL_TREE)
-             err = true;
-         if (err)
+         if (!type_targs_deducible_from (tmpl, ret))
            continue;
        }
 
@@ -30118,6 +30113,60 @@ alias_ctad_tweaks (tree tmpl, tree uguides)
   return aguides;
 }
 
+/* True iff template arguments for TMPL can be deduced from TYPE.
+   Used to implement CPTK_IS_DEDUCIBLE for alias CTAD according to
+   [over.match.class.deduct].
+
+   This check is specified in terms of partial specialization, so the behavior
+   should be parallel to that of get_partial_spec_bindings.  */
+
+bool
+type_targs_deducible_from (tree tmpl, tree type)
+{
+  tree tparms = DECL_INNERMOST_TEMPLATE_PARMS (tmpl);
+  int len = TREE_VEC_LENGTH (tparms);
+  tree targs = make_tree_vec (len);
+  bool tried_array_deduction = (cxx_dialect < cxx17);
+
+  /* If tmpl is a class template, this is trivial: it's deducible if TYPE is a
+     specialization of TMPL.  */
+  if (DECL_CLASS_TEMPLATE_P (tmpl))
+    return (CLASS_TYPE_P (type)
+           && CLASSTYPE_TEMPLATE_INFO (type)
+           && CLASSTYPE_TI_TEMPLATE (type) == tmpl);
+
+  /* Otherwise it's an alias template.  */
+ again:
+  if (unify (tparms, targs, TREE_TYPE (tmpl), type,
+            UNIFY_ALLOW_NONE, false))
+    return false;
+
+  /* We don't fail on an undeduced targ the second time through (like
+     get_partial_spec_bindings) because we're going to try defaults.  */
+  if (!tried_array_deduction)
+    for (int i =  0; i < len; ++i)
+      if (! TREE_VEC_ELT (targs, i))
+       {
+         try_array_deduction (tparms, targs, TREE_TYPE (tmpl));
+         tried_array_deduction = true;
+         if (TREE_VEC_ELT (targs, i))
+           goto again;
+       }
+
+  /* Maybe add in default template args.  This seems like a flaw in the
+     specification in terms of partial specialization, since it says the
+     partial specialization has the the template parameter list of A, but a
+     partial specialization can't have default targs.  */
+  targs = coerce_template_parms (tparms, targs, tmpl, tf_none);
+  if (targs == error_mark_node)
+    return false;
+
+  /* I believe we don't need the template_template_parm_bindings_ok_p call
+     because coerce_template_parms did coerce_template_template_parms.  */
+
+  return constraints_satisfied_p (tmpl, targs);
+}
+
 /* Return artificial deduction guides built from the constructors of class
    template TMPL.  */
 
index db982d594e6d495cbdc93bcba8c2eba69882ec4e..d67a9b26719f28c625b28e0663b8de5d05b76a87 100644 (file)
@@ -12049,6 +12049,9 @@ trait_expr_value (cp_trait_kind kind, tree type1, tree type2)
     case CPTK_REF_CONVERTS_FROM_TEMPORARY:
       return ref_xes_from_temporary (type1, type2, /*direct_init=*/false);
 
+    case CPTK_IS_DEDUCIBLE:
+      return type_targs_deducible_from (type1, type2);
+
 #define DEFTRAIT_TYPE(CODE, NAME, ARITY) \
     case CPTK_##CODE:
 #include "cp-trait.def"
@@ -12206,6 +12209,14 @@ finish_trait_expr (location_t loc, cp_trait_kind kind, tree type1, tree type2)
        return error_mark_node;
       break;
 
+    case CPTK_IS_DEDUCIBLE:
+      if (!DECL_TYPE_TEMPLATE_P (type1))
+       {
+         error ("%qD is not a class or alias template", type1);
+         return error_mark_node;
+       }
+      break;
+
 #define DEFTRAIT_TYPE(CODE, NAME, ARITY) \
     case CPTK_##CODE:
 #include "cp-trait.def"
index cd0dd963532ccbd8ac728ff980b3c12a6f52d943..de83d41e6b4f4bd5de18b956a33c28d0f45786ef 100644 (file)
@@ -4235,7 +4235,7 @@ cp_tree_equal (tree t1, tree t2)
     case TRAIT_EXPR:
       if (TRAIT_EXPR_KIND (t1) != TRAIT_EXPR_KIND (t2))
        return false;
-      return same_type_p (TRAIT_EXPR_TYPE1 (t1), TRAIT_EXPR_TYPE1 (t2))
+      return cp_tree_equal (TRAIT_EXPR_TYPE1 (t1), TRAIT_EXPR_TYPE1 (t2))
        && cp_tree_equal (TRAIT_EXPR_TYPE2 (t1), TRAIT_EXPR_TYPE2 (t2));
 
     case NON_LVALUE_EXPR:
index c11229162551e0b32bd98de76d305fcd3af0e630..b64a85722dbc80cf66d5624ec52c5cb0e8dc7071 100644 (file)
@@ -25213,6 +25213,10 @@ type.  A diagnostic is produced if this requirement is not met.
 If @code{type} is a cv-qualified class type, and not a union type
 ([basic.compound]) the trait is @code{true}, else it is @code{false}.
 
+@item __is_deducible (template, type)
+If template arguments for @code{template} can be deduced from
+@code{type} or obtained from default template arguments.
+
 @item __is_empty (type)
 If @code{__is_class (type)} is @code{false} then the trait is @code{false}.
 Otherwise @code{type} is considered empty if and only if: @code{type}
diff --git a/gcc/testsuite/g++.dg/ext/is_deducible1.C b/gcc/testsuite/g++.dg/ext/is_deducible1.C
new file mode 100644 (file)
index 0000000..30cbe08
--- /dev/null
@@ -0,0 +1,31 @@
+// { dg-do compile { target c++20 } }
+
+template <class T> struct A { };
+template <class T> struct B { };
+
+// Simple forms.
+static_assert (__is_deducible (::A, A<int>));
+static_assert (__is_deducible (B, B<int>));
+static_assert (!__is_deducible (A, B<int>));
+static_assert (!__is_deducible (::B, A<int>));
+
+// This is the interesting use case for alias CTAD.
+template <class T> using AP = A<T*>;
+static_assert (__is_deducible (AP, A<int*>));
+static_assert (!__is_deducible (AP, A<int>));
+
+// Can't deduce a parameter not used on the RHS.
+template <class T> using C = void;
+static_assert (!__is_deducible (C, C<int>));
+
+// But a default template argument counts.
+template <class T = void> using D = void;
+static_assert (__is_deducible (D, D<int>));
+
+// P0127 array bound type deduction should work here.
+template <class T, T N> using E = int[N];
+static_assert (__is_deducible (E, int[42]));
+
+// We don't try to support this.
+template <class T> void f(T);
+bool b = __is_deducible (f, void (int)); // { dg-error "class or alias" }