On Sat, 18 Feb 2023, Jason Merrill via Gcc-patches wrote:

> Tested x86_64-pc-linux-gnu.  Since this is fixing experimental (C++20)
> functionality, I think it's reasonable to apply now; I'm interested in other
> opinions, and thoughts about the user-facing functionality.  I'm thinking to
> make it internal-only for GCC 13 at least by adding a space in the name, but
> does this look useful to the library?

IIUC this looks like a generalization of an __is_specialization_of trait
that returns whether a type is a specialization of a given class template,
which seems potentially useful for the library to me.  We already define
some ad-hoc predicates for testing this, e.g. __is_reverse_view,
__is_span etc in <ranges> as well as a more general __is_specialization_of
in <format> for templates that take only type arguments.  Using a built-in
trait should be more efficient.

> 
> -- 8< --
> 
> 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.
>       * 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/doc/extend.texi                      |  4 +++
>  gcc/cp/cp-tree.h                         |  1 +
>  gcc/cp/constraint.cc                     |  3 ++
>  gcc/cp/cxx-pretty-print.cc               |  5 +++-
>  gcc/cp/parser.cc                         | 20 +++++++++++---
>  gcc/cp/pt.cc                             | 35 +++++++++++++++++-------
>  gcc/cp/semantics.cc                      | 11 ++++++++
>  gcc/testsuite/g++.dg/ext/is_deducible1.C | 27 ++++++++++++++++++
>  gcc/cp/cp-trait.def                      |  1 +
>  9 files changed, 92 insertions(+), 15 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/ext/is_deducible1.C
> 
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index 1ae68b0f20a..898701424ad 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -25207,6 +25207,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/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 5595335bbf7..e79150ca4d8 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7372,6 +7372,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);
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 9374327008b..a28c85178fe 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -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"
> diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
> index bea52a608f1..4ebd957decd 100644
> --- a/gcc/cp/cxx-pretty-print.cc
> +++ b/gcc/cp/cxx-pretty-print.cc
> @@ -2626,7 +2626,10 @@ pp_cxx_trait (cxx_pretty_printer *pp, tree t)
>      }
>  
>    pp_cxx_left_paren (pp);
> -  pp->type_id (type1);
> +  if (DECL_P (type1))
> +    pp->expression (type1);
> +  else
> +    pp->type_id (type1);

Since the first argument of a TRAIT_EXPR can now be a TEMPLATE_DECL, I
suppose cp_tree_equal needs to be changed too.

>    if (type2)
>      {
>        if (TREE_CODE (type2) != TREE_LIST)
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index 1a124f5395e..68950cace78 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -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;
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index b1ac7d4beb4..2aa06557b99 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -21577,8 +21577,9 @@ tsubst_copy_and_build (tree t,
>  
>      case TRAIT_EXPR:
>        {
> -     tree type1 = tsubst (TRAIT_EXPR_TYPE1 (t), args,
> -                          complain, in_decl);
> +     tree type1 = TRAIT_EXPR_TYPE1 (t);
> +     if (TREE_CODE (type1) != TEMPLATE_DECL)
> +       type1 = tsubst (type1, args, complain, in_decl);
>       tree type2 = tsubst (TRAIT_EXPR_TYPE2 (t), args,
>                            complain, in_decl);
>       RETURN (finish_trait_expr (TRAIT_EXPR_LOCATION (t),
> @@ -29979,7 +29980,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
> @@ -30079,7 +30080,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);
>           }
>  
> @@ -30093,12 +30094,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;
>       }
>  
> @@ -30108,6 +30104,25 @@ 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.  */
> +
> +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);
> +  if (unify (tparms, targs, TREE_TYPE (tmpl), type,
> +          UNIFY_ALLOW_NONE, false))
> +    return false;
> +  /* Maybe add in default template args.  */
> +  targs = coerce_template_parms (tparms, targs, tmpl, tf_none);
> +  if (targs == error_mark_node)
> +    return false;
> +  return constraints_satisfied_p (tmpl, targs);
> +}

For sake of the __is_specialization_of use case, I wonder if it'd
be possible to have a "fast path" that avoids deduction/coercion when
the given template is a class template?

> +
>  /* Return artificial deduction guides built from the constructors of class
>     template TMPL.  */
>  
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index 79b7cc72f21..9103f5de2f4 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -12048,6 +12048,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"
> @@ -12205,6 +12208,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"
> diff --git a/gcc/testsuite/g++.dg/ext/is_deducible1.C 
> b/gcc/testsuite/g++.dg/ext/is_deducible1.C
> new file mode 100644
> index 00000000000..857f59db4c8
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/is_deducible1.C
> @@ -0,0 +1,27 @@
> +// { 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>));
> +
> +// 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" }
> diff --git a/gcc/cp/cp-trait.def b/gcc/cp/cp-trait.def
> index 823899a26c5..e43fb464f42 100644
> --- a/gcc/cp/cp-trait.def
> +++ b/gcc/cp/cp-trait.def
> @@ -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)
> 
> base-commit: 9944ca17c0766623bce260684edc614def7ea761
> -- 
> 2.31.1
> 
> 

Reply via email to