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 > >