Message ID | 20231122220731.1121607-1-ppalka@redhat.com |
---|---|
State | New |
Headers | show |
Series | c++: Implement P2582R1, CTAD from inherited constructors | expand |
On Wed, 22 Nov 2023, Patrick Palka wrote: > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for > trunk? > > -- >8 -- > > This patch implements C++23 class template argument deduction from > inherited constructors, which is specified in terms of C++20 alias > CTAD which we already fully support. The rule for transforming > the return type of an inherited guide is specified in terms of a > partially specialized class template, but this patch implements it > in a simpler way, performing ahead of time deduction instead of > instantiation time deduction. I wasn't able to find an example for > which this implementation strategy makes a difference, but I didn't > look very hard. Support seems good enough to advertise as complete, > and there should be no functional change before C++23 mode. > > There's a couple of FIXMEs, one in inherited_ctad_tweaks for recognizing > more forms of inherited constructors, and one in deduction_guides_for for > making the cache aware of base-class dependencies. > > There doesn't seem to be a feature-test macro update for this paper. > Here's v2 with some minor changes: * set processing_template_decl when rewriting the return type of a template guide * rather than adding an out parameter to type_targs_deducible_from, just make it return NULL_TREE or the deduced args * add a testcase demonstrating each of the FIXMEs -- >8 -- gcc/cp/ChangeLog: * cp-tree.h (type_targs_deducible_from): Adjust return type. * pt.cc (alias_ctad_tweaks): Handle C++23 inherited CTAD. (inherited_ctad_tweaks): Define. (type_targs_deducible_from): Return the deduced arguments or NULL_TREE instead of a bool. Handle 'tmpl' being a TREE_LIST representing a synthetic alias template. (ctor_deduction_guides_for): Do inherited_ctad_tweaks for each USING_DECL in C++23 mode. (deduction_guides_for): Add FIXME for stale cache entries in light of inherited CTAD. gcc/testsuite/ChangeLog: * g++.dg/cpp1z/class-deduction67.C: Accept in C++23 mode. * g++.dg/cpp23/class-deduction-inherited1.C: New test. * g++.dg/cpp23/class-deduction-inherited2.C: New test. * g++.dg/cpp23/class-deduction-inherited3.C: New test. * g++.dg/cpp23/class-deduction-inherited4.C: New test. --- gcc/cp/cp-tree.h | 2 +- gcc/cp/pt.cc | 186 +++++++++++++++--- .../g++.dg/cpp1z/class-deduction67.C | 5 +- .../g++.dg/cpp23/class-deduction-inherited1.C | 38 ++++ .../g++.dg/cpp23/class-deduction-inherited2.C | 26 +++ .../g++.dg/cpp23/class-deduction-inherited3.C | 16 ++ .../g++.dg/cpp23/class-deduction-inherited4.C | 32 +++ 7 files changed, 272 insertions(+), 33 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 7b0b7c6a17e..abc467fb290 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -7457,7 +7457,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 tree 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/pt.cc b/gcc/cp/pt.cc index 092e6fdfd36..8b7aa96cf01 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -223,6 +223,9 @@ static void instantiate_body (tree pattern, tree args, tree d, bool nested); static tree maybe_dependent_member_ref (tree, tree, tsubst_flags_t, tree); static void mark_template_arguments_used (tree, tree); static bool uses_outer_template_parms (tree); +static tree alias_ctad_tweaks (tree, tree); +static tree inherited_ctad_tweaks (tree, tree, tsubst_flags_t); +static tree deduction_guides_for (tree, bool&, tsubst_flags_t); /* Make the current scope suitable for access checking when we are processing T. T can be FUNCTION_DECL for instantiated function @@ -29736,8 +29739,6 @@ is_spec_or_derived (tree etype, tree tmpl) return !err; } -static tree alias_ctad_tweaks (tree, tree); - /* Return a C++20 aggregate deduction candidate for TYPE initialized from INIT. */ @@ -29842,7 +29843,13 @@ maybe_aggr_guide (tree tmpl, tree init, vec<tree,va_gc> *args) } /* UGUIDES are the deduction guides for the underlying template of alias - template TMPL; adjust them to be deduction guides for TMPL. */ + template TMPL; adjust them to be deduction guides for TMPL. + + This routine also handles C++23 inherited CTAD, in which case TMPL is a + TREE_LIST representing a synthetic alias template whose TREE_PURPOSE is + the template parameter list of the alias template (equivalently, of the + derived class) and TREE_VALUE the defining-type-id (equivalently, the + base whose guides we're inheriting). UGUIDES are the base's guides. */ static tree alias_ctad_tweaks (tree tmpl, tree uguides) @@ -29886,13 +29893,27 @@ alias_ctad_tweaks (tree tmpl, tree uguides) * The explicit-specifier of f' is the explicit-specifier of g (if any). */ + enum { alias, inherited } ctad_kind; + tree atype, fullatparms, utype; + if (TREE_CODE (tmpl) == TEMPLATE_DECL) + { + ctad_kind = alias; + atype = TREE_TYPE (tmpl); + fullatparms = DECL_TEMPLATE_PARMS (tmpl); + utype = DECL_ORIGINAL_TYPE (DECL_TEMPLATE_RESULT (tmpl)); + } + else + { + ctad_kind = inherited; + atype = NULL_TREE; + fullatparms = TREE_PURPOSE (tmpl); + utype = TREE_VALUE (tmpl); + } + tsubst_flags_t complain = tf_warning_or_error; - tree atype = TREE_TYPE (tmpl); tree aguides = NULL_TREE; - tree fullatparms = DECL_TEMPLATE_PARMS (tmpl); tree atparms = INNERMOST_TEMPLATE_PARMS (fullatparms); unsigned natparms = TREE_VEC_LENGTH (atparms); - tree utype = DECL_ORIGINAL_TYPE (DECL_TEMPLATE_RESULT (tmpl)); for (ovl_iterator iter (uguides); iter; ++iter) { tree f = *iter; @@ -29930,7 +29951,7 @@ alias_ctad_tweaks (tree tmpl, tree uguides) /* Set current_template_parms as in build_deduction_guide. */ auto ctp = make_temp_override (current_template_parms); - current_template_parms = copy_node (DECL_TEMPLATE_PARMS (tmpl)); + current_template_parms = copy_node (fullatparms); TREE_VALUE (current_template_parms) = gtparms; j = 0; @@ -30006,9 +30027,10 @@ alias_ctad_tweaks (tree tmpl, tree uguides) /* Add a constraint that the return type matches the instantiation of A with the same template arguments. */ ret = TREE_TYPE (TREE_TYPE (fprime)); - if (!same_type_p (atype, ret) - /* FIXME this should mean they don't compare as equivalent. */ - || dependent_alias_template_spec_p (atype, nt_opaque)) + if (ctad_kind == alias + && (!same_type_p (atype, ret) + /* 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_DEDUCIBLE, tmpl, ret); ci = append_constraint (ci, same); @@ -30025,43 +30047,134 @@ 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. */ non_template: - if (!type_targs_deducible_from (tmpl, ret)) + if (ctad_kind == alias + && !type_targs_deducible_from (tmpl, ret)) continue; } + /* Rewrite the return type of the inherited guide in terms of the + derived class. This is specified as replacing the return type R + with typename CC<R>::type where the partially specialized CC maps a + base class specialization to a specialization of the derived class + having such a base (inducing substitution failure if no such derived + class exists). + + As specified this mapping would be done at instantiation time using + non-dependent template arguments, but we do it ahead of time using + the generic arguments. This seems to be good enough since generic + deduction should succeed only if concrete deduction would. */ + if (ctad_kind == inherited) + { + processing_template_decl_sentinel ptds (/*reset*/false); + if (TREE_CODE (fprime) == TEMPLATE_DECL) + ++processing_template_decl; + + if (TREE_CODE (f) != TEMPLATE_DECL) + fprime = copy_decl (fprime); + else + /* Our fprime is already a copy. */; + + tree fntype = TREE_TYPE (fprime); + ret = lookup_template_class (TPARMS_PRIMARY_TEMPLATE (atparms), targs, + in_decl, NULL_TREE, false, complain); + fntype = build_function_type (ret, TYPE_ARG_TYPES (fntype)); + TREE_TYPE (fprime) = fntype; + if (TREE_CODE (fprime) == TEMPLATE_DECL) + TREE_TYPE (DECL_TEMPLATE_RESULT (fprime)) = fntype; + } + aguides = lookup_add (fprime, aguides); } return aguides; } -/* True iff template arguments for TMPL can be deduced from TYPE. +/* CTOR is a using-declaration inheriting the constructors of some base of the + class template TMPL; adjust the base's guides be deduction guides for TMPL. */ + +static tree +inherited_ctad_tweaks (tree tmpl, tree ctor, tsubst_flags_t complain) +{ + /* [over.match.class.deduct]: In addition, if C is defined and inherits + constructors ([namespace.udecl]) from a direct base class denoted in the + base-specifier-list by a class-or-decltype B, let A be an alias template + whose template parameter list is that of C and whose defining-type-id is + B. If A is a deducible template ([dcl.type.simple]), the set contains the + guides of A with the return type R of each guide replaced with typename + CC::type given a class template + + template <typename> class CC; + + whose primary template is not defined and with a single partial + specialization whose template parameter list is that of A and whose + template argument list is a specialization of A with the template argument + list of A ([temp.dep.type]) having a member typedef type designating a + template specialization with the template argument list of A but with C as + the template. */ + + /* FIXME: Also recognize inherited constructors of the form 'using C::B::B', + which seem to be represented with TYPENAME_TYPE C::B as USING_DECL_SCOPE? + And recognize constructors inherited from a non-dependent base class, which + seem to be missing from the overload set entirely? */ + tree scope = USING_DECL_SCOPE (ctor); + if (!CLASS_TYPE_P (scope) + || !CLASSTYPE_TEMPLATE_INFO (scope)) + return NULL_TREE; + + tree t = build_tree_list (DECL_TEMPLATE_PARMS (tmpl), scope); + bool any_dguides_p; + tree uguides = deduction_guides_for (TYPE_TI_TEMPLATE (scope), + any_dguides_p, complain); + return alias_ctad_tweaks (t, uguides); +} + +/* If template arguments for TMPL can be deduced from TYPE, return + the deduced arguments, otherwise return NULL_TREE. 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 +tree type_targs_deducible_from (tree tmpl, tree type) { - tree tparms = DECL_INNERMOST_TEMPLATE_PARMS (tmpl); + tree tparms, ttype; + if (TREE_CODE (tmpl) == TEMPLATE_DECL) + { + /* 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)) + { + if (CLASS_TYPE_P (type) + && CLASSTYPE_TEMPLATE_INFO (type) + && CLASSTYPE_TI_TEMPLATE (type) == tmpl) + return INNERMOST_TEMPLATE_ARGS (CLASSTYPE_TI_ARGS (type)); + else + return NULL_TREE; + } + + /* Otherwise it's an alias template. */ + tparms = DECL_INNERMOST_TEMPLATE_PARMS (tmpl); + ttype = TREE_TYPE (tmpl); + } + else + { + /* TMPL is a synthetic alias template represented as a TREE_LIST as + per alias_ctad_tweaks. */ + tparms = INNERMOST_TEMPLATE_PARMS (TREE_PURPOSE (tmpl)); + ttype = TREE_VALUE (tmpl); + tmpl = TI_TEMPLATE (TYPE_TEMPLATE_INFO_MAYBE_ALIAS (ttype)); + } + 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, + if (unify (tparms, targs, ttype, type, UNIFY_ALLOW_NONE, false)) - return false; + return NULL_TREE; /* We don't fail on an undeduced targ the second time through (like get_partial_spec_bindings) because we're going to try defaults. */ @@ -30074,7 +30187,7 @@ type_targs_deducible_from (tree tmpl, tree type) if (!tried_array_deduction && TREE_CODE (tparm) == TYPE_DECL) { - try_array_deduction (tparms, targs, TREE_TYPE (tmpl)); + try_array_deduction (tparms, targs, ttype); tried_array_deduction = true; if (TREE_VEC_ELT (targs, i)) goto again; @@ -30103,12 +30216,15 @@ type_targs_deducible_from (tree tmpl, tree type) partial specialization can't have default targs. */ targs = coerce_template_parms (tparms, targs, tmpl, tf_none); if (targs == error_mark_node) - return false; + return NULL_TREE; /* 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); + if (!constraints_satisfied_p (tmpl, targs)) + return NULL_TREE; + + return targs; } /* Return artificial deduction guides built from the constructors of class @@ -30124,7 +30240,7 @@ ctor_deduction_guides_for (tree tmpl, tsubst_flags_t complain) for (ovl_iterator iter (CLASSTYPE_CONSTRUCTORS (type)); iter; ++iter) { - /* Skip inherited constructors. */ + /* We handle C++23 inherited CTAD below. */ if (iter.using_p ()) continue; @@ -30132,6 +30248,15 @@ ctor_deduction_guides_for (tree tmpl, tsubst_flags_t complain) cands = lookup_add (guide, cands); } + if (cxx_dialect >= cxx23) + for (tree ctor : ovl_range (CLASSTYPE_CONSTRUCTORS (type))) + if (TREE_CODE (ctor) == USING_DECL) + { + tree uguides = inherited_ctad_tweaks (tmpl, ctor, complain); + if (uguides) + cands = lookup_add (uguides, cands); + } + /* Add implicit default constructor deduction guide. */ if (!TYPE_HAS_USER_CONSTRUCTOR (type)) { @@ -30181,7 +30306,10 @@ deduction_guides_for (tree tmpl, bool &any_dguides_p, tsubst_flags_t complain) } /* Cache the deduction guides for a template. We also remember the result of - lookup, and rebuild everything if it changes; should be very rare. */ + lookup, and rebuild everything if it changes; should be very rare. + + FIXME: Also rebuild if this is a class template that inherits guides from a + base class, and lookup for the latter changed. */ tree_pair_p cache = NULL; if (tree_pair_p &r = hash_map_safe_get_or_insert<hm_ggc> (dguide_cache, tmpl)) diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C index 4624794c4b7..74f92325d7a 100644 --- a/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C @@ -1,5 +1,4 @@ -// Deduction from inherited constructors isn't supported yet, but we shouldn't -// crash. It may well be supported in C++23. +// Deduction from inherited constructors isn't supported before C++23. //{ dg-do compile { target c++17 } } @@ -17,5 +16,5 @@ int main() { B b = 42; // { dg-line init } // { dg-prune-output "no matching function" } - // { dg-error "class template argument deduction" "" { target *-*-* } init } + // { dg-error "class template argument deduction" "" { target c++23_down } init } } diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C new file mode 100644 index 00000000000..5fd1270e819 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C @@ -0,0 +1,38 @@ +// Modified example from P2582R1 +// { dg-do compile { target c++23 } } + +template <typename T> struct B { + B(T); +}; +B(bool) -> B<char>; +template <typename T> struct C : public B<T> { + using B<T>::B; +}; +template <typename T> struct D : public B<T> {}; + +C c(42); // OK, deduces C<int> +using ty1 = decltype(c); +using ty1 = C<int>; + +D d(42); // { dg-error "deduction|no match" } + +C c2(true); // OK, deduces C<char> +using ty2 = decltype(c2); +using ty2 = C<char>; + +template <typename T> struct E : public B<int> { + using B<int>::B; +}; + +E e(42); // { dg-error "deduction|no match" } + +template <typename T, typename U, typename V> struct F { + F(T, U, V); +}; +template <typename T, typename U> struct G : F<U, T, int> { + using F<U, T, int>::F; +}; + +G g(true, 'a', 1); // OK, deduces G<char, bool> +using ty3 = decltype(g); +using ty3 = G<char, bool>; diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C new file mode 100644 index 00000000000..cb3c595f316 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C @@ -0,0 +1,26 @@ +// { dg-do compile { target c++23 } } + +template<class T, class U, class V> struct F { + F(T, U, V); // #1 + F(T*, U*, V*); // #2 + template<class W> + F(int, int, W); // #3 +}; + +F(bool, bool, bool) -> F<bool*, void*, int>; + +template<class T, class U> struct G : F<U, T, int> { + using F<U, T, int>::F; +}; + +using ty1 = decltype(G(true, 'a', 1)); // uses #1 +using ty1 = G<char, bool>; + +using ty2 = decltype(G((bool*)0, (char*)0, (int*)0)); // uses #2 +using ty2 = G<char, bool>; + +using ty3 = decltype(G(0, 0, 0)); // uses #3 +using ty3 = G<int, int>; + +using ty4 = decltype(G(true, true, true)); // uses #4 +using ty4 = G<void*, bool*>; diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C new file mode 100644 index 00000000000..57e323b5124 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C @@ -0,0 +1,16 @@ +// { dg-do compile { target c++23 } } + +template<class T> +struct A { + A(T); + template<class U> A(T, U); +}; + +template<class T> +struct B : A<const T> { + using A<const T>::A; +}; + +using type = decltype(B(0)); +using type = decltype(B(0, 0)); +using type = B<int>; diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C new file mode 100644 index 00000000000..f706ebe9bb7 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C @@ -0,0 +1,32 @@ +// { dg-do compile { target c++23 } } + +template<class T> +struct A { A(T); }; + +template<class T> +struct B : A<T> { + using B::A::A; // FIXME: we don't notice this inherited ctor during CTAD +}; + +using ty1 = decltype(B(0)); // { dg-bogus "" "" { xfail *-*-* } } +using ty1 = B<int>; + +template<class T=void> +struct C : A<int> { + using A<int>::A; // FIXME: we don't notice this one either +}; + +using ty2 = decltype(C(0)); // { dg-bogus "" "" { xfail *-*-* } } +using ty2 = C<void>; + +template<class T> +struct D : A<T> { + using A<T>::A; +}; + +using ty3 = decltype(D(0)); +using ty3 = D<int>; + +A(int) -> A<char>; // FIXME: we need to rebuild the guides of D +using ty4 = decltype(D(0)); +using ty4 = D<char>; // { dg-bogus "conflicting" "" { xfail *-*-* } }
On Fri, 24 Nov 2023, Patrick Palka wrote: > On Wed, 22 Nov 2023, Patrick Palka wrote: > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK for > > trunk? > > > > -- >8 -- > > > > This patch implements C++23 class template argument deduction from > > inherited constructors, which is specified in terms of C++20 alias > > CTAD which we already fully support. The rule for transforming > > the return type of an inherited guide is specified in terms of a > > partially specialized class template, but this patch implements it > > in a simpler way, performing ahead of time deduction instead of > > instantiation time deduction. I wasn't able to find an example for > > which this implementation strategy makes a difference, but I didn't > > look very hard. Support seems good enough to advertise as complete, > > and there should be no functional change before C++23 mode. > > > > There's a couple of FIXMEs, one in inherited_ctad_tweaks for recognizing > > more forms of inherited constructors, and one in deduction_guides_for for > > making the cache aware of base-class dependencies. > > > > There doesn't seem to be a feature-test macro update for this paper. > > > > Here's v2 with some minor changes: > > * set processing_template_decl when rewriting the return type of > a template guide > * rather than adding an out parameter to type_targs_deducible_from, > just make it return NULL_TREE or the deduced args > * add a testcase demonstrating each of the FIXMEs > > -- >8 -- > > gcc/cp/ChangeLog: > > * cp-tree.h (type_targs_deducible_from): Adjust return type. > * pt.cc (alias_ctad_tweaks): Handle C++23 inherited CTAD. > (inherited_ctad_tweaks): Define. > (type_targs_deducible_from): Return the deduced arguments or > NULL_TREE instead of a bool. Handle 'tmpl' being a TREE_LIST > representing a synthetic alias template. > (ctor_deduction_guides_for): Do inherited_ctad_tweaks for each > USING_DECL in C++23 mode. > (deduction_guides_for): Add FIXME for stale cache entries in > light of inherited CTAD. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp1z/class-deduction67.C: Accept in C++23 mode. > * g++.dg/cpp23/class-deduction-inherited1.C: New test. > * g++.dg/cpp23/class-deduction-inherited2.C: New test. > * g++.dg/cpp23/class-deduction-inherited3.C: New test. > * g++.dg/cpp23/class-deduction-inherited4.C: New test. > --- > gcc/cp/cp-tree.h | 2 +- > gcc/cp/pt.cc | 186 +++++++++++++++--- > .../g++.dg/cpp1z/class-deduction67.C | 5 +- > .../g++.dg/cpp23/class-deduction-inherited1.C | 38 ++++ > .../g++.dg/cpp23/class-deduction-inherited2.C | 26 +++ > .../g++.dg/cpp23/class-deduction-inherited3.C | 16 ++ > .../g++.dg/cpp23/class-deduction-inherited4.C | 32 +++ > 7 files changed, 272 insertions(+), 33 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C > create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C > create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C > create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h > index 7b0b7c6a17e..abc467fb290 100644 > --- a/gcc/cp/cp-tree.h > +++ b/gcc/cp/cp-tree.h > @@ -7457,7 +7457,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 tree 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/pt.cc b/gcc/cp/pt.cc > index 092e6fdfd36..8b7aa96cf01 100644 > --- a/gcc/cp/pt.cc > +++ b/gcc/cp/pt.cc > @@ -223,6 +223,9 @@ static void instantiate_body (tree pattern, tree args, tree d, bool nested); > static tree maybe_dependent_member_ref (tree, tree, tsubst_flags_t, tree); > static void mark_template_arguments_used (tree, tree); > static bool uses_outer_template_parms (tree); > +static tree alias_ctad_tweaks (tree, tree); > +static tree inherited_ctad_tweaks (tree, tree, tsubst_flags_t); > +static tree deduction_guides_for (tree, bool&, tsubst_flags_t); > > /* Make the current scope suitable for access checking when we are > processing T. T can be FUNCTION_DECL for instantiated function > @@ -29736,8 +29739,6 @@ is_spec_or_derived (tree etype, tree tmpl) > return !err; > } > > -static tree alias_ctad_tweaks (tree, tree); > - > /* Return a C++20 aggregate deduction candidate for TYPE initialized from > INIT. */ > > @@ -29842,7 +29843,13 @@ maybe_aggr_guide (tree tmpl, tree init, vec<tree,va_gc> *args) > } > > /* UGUIDES are the deduction guides for the underlying template of alias > - template TMPL; adjust them to be deduction guides for TMPL. */ > + template TMPL; adjust them to be deduction guides for TMPL. > + > + This routine also handles C++23 inherited CTAD, in which case TMPL is a > + TREE_LIST representing a synthetic alias template whose TREE_PURPOSE is > + the template parameter list of the alias template (equivalently, of the > + derived class) and TREE_VALUE the defining-type-id (equivalently, the > + base whose guides we're inheriting). UGUIDES are the base's guides. */ > > static tree > alias_ctad_tweaks (tree tmpl, tree uguides) > @@ -29886,13 +29893,27 @@ alias_ctad_tweaks (tree tmpl, tree uguides) > * The explicit-specifier of f' is the explicit-specifier of g (if > any). */ > > + enum { alias, inherited } ctad_kind; > + tree atype, fullatparms, utype; > + if (TREE_CODE (tmpl) == TEMPLATE_DECL) > + { > + ctad_kind = alias; > + atype = TREE_TYPE (tmpl); > + fullatparms = DECL_TEMPLATE_PARMS (tmpl); > + utype = DECL_ORIGINAL_TYPE (DECL_TEMPLATE_RESULT (tmpl)); > + } > + else > + { > + ctad_kind = inherited; > + atype = NULL_TREE; > + fullatparms = TREE_PURPOSE (tmpl); > + utype = TREE_VALUE (tmpl); > + } > + > tsubst_flags_t complain = tf_warning_or_error; > - tree atype = TREE_TYPE (tmpl); > tree aguides = NULL_TREE; > - tree fullatparms = DECL_TEMPLATE_PARMS (tmpl); > tree atparms = INNERMOST_TEMPLATE_PARMS (fullatparms); > unsigned natparms = TREE_VEC_LENGTH (atparms); > - tree utype = DECL_ORIGINAL_TYPE (DECL_TEMPLATE_RESULT (tmpl)); > for (ovl_iterator iter (uguides); iter; ++iter) > { > tree f = *iter; > @@ -29930,7 +29951,7 @@ alias_ctad_tweaks (tree tmpl, tree uguides) > > /* Set current_template_parms as in build_deduction_guide. */ > auto ctp = make_temp_override (current_template_parms); > - current_template_parms = copy_node (DECL_TEMPLATE_PARMS (tmpl)); > + current_template_parms = copy_node (fullatparms); > TREE_VALUE (current_template_parms) = gtparms; > > j = 0; > @@ -30006,9 +30027,10 @@ alias_ctad_tweaks (tree tmpl, tree uguides) > /* Add a constraint that the return type matches the instantiation of > A with the same template arguments. */ > ret = TREE_TYPE (TREE_TYPE (fprime)); > - if (!same_type_p (atype, ret) > - /* FIXME this should mean they don't compare as equivalent. */ > - || dependent_alias_template_spec_p (atype, nt_opaque)) > + if (ctad_kind == alias > + && (!same_type_p (atype, ret) > + /* 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_DEDUCIBLE, tmpl, ret); > ci = append_constraint (ci, same); > @@ -30025,43 +30047,134 @@ 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. */ > non_template: > - if (!type_targs_deducible_from (tmpl, ret)) > + if (ctad_kind == alias > + && !type_targs_deducible_from (tmpl, ret)) > continue; > } > > + /* Rewrite the return type of the inherited guide in terms of the > + derived class. This is specified as replacing the return type R > + with typename CC<R>::type where the partially specialized CC maps a > + base class specialization to a specialization of the derived class > + having such a base (inducing substitution failure if no such derived > + class exists). > + > + As specified this mapping would be done at instantiation time using > + non-dependent template arguments, but we do it ahead of time using > + the generic arguments. This seems to be good enough since generic > + deduction should succeed only if concrete deduction would. */ > + if (ctad_kind == inherited) > + { > + processing_template_decl_sentinel ptds (/*reset*/false); > + if (TREE_CODE (fprime) == TEMPLATE_DECL) > + ++processing_template_decl; > + > + if (TREE_CODE (f) != TEMPLATE_DECL) > + fprime = copy_decl (fprime); > + else > + /* Our fprime is already a copy. */; > + > + tree fntype = TREE_TYPE (fprime); > + ret = lookup_template_class (TPARMS_PRIMARY_TEMPLATE (atparms), targs, > + in_decl, NULL_TREE, false, complain); Oops, the local variable targs is missing from this block due to a botched git commit --amend. Here's the corrected v2 patch: -- >8 -- gcc/cp/ChangeLog: * cp-tree.h (type_targs_deducible_from): Adjust return type. * pt.cc (alias_ctad_tweaks): Handle C++23 inherited CTAD. (inherited_ctad_tweaks): Define. (type_targs_deducible_from): Return the deduced arguments or NULL_TREE instead of a bool. Handle 'tmpl' being a TREE_LIST representing a synthetic alias template. (ctor_deduction_guides_for): Do inherited_ctad_tweaks for each USING_DECL in C++23 mode. (deduction_guides_for): Add FIXME for stale cache entries in light of inherited CTAD. gcc/testsuite/ChangeLog: * g++.dg/cpp1z/class-deduction67.C: Accept in C++23 mode. * g++.dg/cpp23/class-deduction-inherited1.C: New test. * g++.dg/cpp23/class-deduction-inherited2.C: New test. * g++.dg/cpp23/class-deduction-inherited3.C: New test. * g++.dg/cpp23/class-deduction-inherited4.C: New test. --- gcc/cp/cp-tree.h | 2 +- gcc/cp/pt.cc | 187 +++++++++++++++--- .../g++.dg/cpp1z/class-deduction67.C | 5 +- .../g++.dg/cpp23/class-deduction-inherited1.C | 38 ++++ .../g++.dg/cpp23/class-deduction-inherited2.C | 26 +++ .../g++.dg/cpp23/class-deduction-inherited3.C | 16 ++ .../g++.dg/cpp23/class-deduction-inherited4.C | 32 +++ 7 files changed, 273 insertions(+), 33 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 7b0b7c6a17e..abc467fb290 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -7457,7 +7457,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 tree 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/pt.cc b/gcc/cp/pt.cc index 092e6fdfd36..5b982d1d641 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -223,6 +223,9 @@ static void instantiate_body (tree pattern, tree args, tree d, bool nested); static tree maybe_dependent_member_ref (tree, tree, tsubst_flags_t, tree); static void mark_template_arguments_used (tree, tree); static bool uses_outer_template_parms (tree); +static tree alias_ctad_tweaks (tree, tree); +static tree inherited_ctad_tweaks (tree, tree, tsubst_flags_t); +static tree deduction_guides_for (tree, bool&, tsubst_flags_t); /* Make the current scope suitable for access checking when we are processing T. T can be FUNCTION_DECL for instantiated function @@ -29736,8 +29739,6 @@ is_spec_or_derived (tree etype, tree tmpl) return !err; } -static tree alias_ctad_tweaks (tree, tree); - /* Return a C++20 aggregate deduction candidate for TYPE initialized from INIT. */ @@ -29842,7 +29843,13 @@ maybe_aggr_guide (tree tmpl, tree init, vec<tree,va_gc> *args) } /* UGUIDES are the deduction guides for the underlying template of alias - template TMPL; adjust them to be deduction guides for TMPL. */ + template TMPL; adjust them to be deduction guides for TMPL. + + This routine also handles C++23 inherited CTAD, in which case TMPL is a + TREE_LIST representing a synthetic alias template whose TREE_PURPOSE is + the template parameter list of the alias template (equivalently, of the + derived class) and TREE_VALUE the defining-type-id (equivalently, the + base whose guides we're inheriting). UGUIDES are the base's guides. */ static tree alias_ctad_tweaks (tree tmpl, tree uguides) @@ -29886,13 +29893,27 @@ alias_ctad_tweaks (tree tmpl, tree uguides) * The explicit-specifier of f' is the explicit-specifier of g (if any). */ + enum { alias, inherited } ctad_kind; + tree atype, fullatparms, utype; + if (TREE_CODE (tmpl) == TEMPLATE_DECL) + { + ctad_kind = alias; + atype = TREE_TYPE (tmpl); + fullatparms = DECL_TEMPLATE_PARMS (tmpl); + utype = DECL_ORIGINAL_TYPE (DECL_TEMPLATE_RESULT (tmpl)); + } + else + { + ctad_kind = inherited; + atype = NULL_TREE; + fullatparms = TREE_PURPOSE (tmpl); + utype = TREE_VALUE (tmpl); + } + tsubst_flags_t complain = tf_warning_or_error; - tree atype = TREE_TYPE (tmpl); tree aguides = NULL_TREE; - tree fullatparms = DECL_TEMPLATE_PARMS (tmpl); tree atparms = INNERMOST_TEMPLATE_PARMS (fullatparms); unsigned natparms = TREE_VEC_LENGTH (atparms); - tree utype = DECL_ORIGINAL_TYPE (DECL_TEMPLATE_RESULT (tmpl)); for (ovl_iterator iter (uguides); iter; ++iter) { tree f = *iter; @@ -29930,7 +29951,7 @@ alias_ctad_tweaks (tree tmpl, tree uguides) /* Set current_template_parms as in build_deduction_guide. */ auto ctp = make_temp_override (current_template_parms); - current_template_parms = copy_node (DECL_TEMPLATE_PARMS (tmpl)); + current_template_parms = copy_node (fullatparms); TREE_VALUE (current_template_parms) = gtparms; j = 0; @@ -30006,9 +30027,10 @@ alias_ctad_tweaks (tree tmpl, tree uguides) /* Add a constraint that the return type matches the instantiation of A with the same template arguments. */ ret = TREE_TYPE (TREE_TYPE (fprime)); - if (!same_type_p (atype, ret) - /* FIXME this should mean they don't compare as equivalent. */ - || dependent_alias_template_spec_p (atype, nt_opaque)) + if (ctad_kind == alias + && (!same_type_p (atype, ret) + /* 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_DEDUCIBLE, tmpl, ret); ci = append_constraint (ci, same); @@ -30025,8 +30047,41 @@ 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. */ non_template: - if (!type_targs_deducible_from (tmpl, ret)) + if (ctad_kind == alias + && !type_targs_deducible_from (tmpl, ret)) + continue; + } + + /* Rewrite the return type of the inherited guide in terms of the + derived class. This is specified as replacing the return type R + with typename CC<R>::type where the partially specialized CC maps a + base class specialization to a specialization of the derived class + having such a base (inducing substitution failure if no such derived + class exists). + + As specified this mapping would be done at instantiation time using + non-dependent template arguments, but we do it ahead of time using + the generic arguments. This seems to be good enough since generic + deduction should succeed only if concrete deduction would. */ + if (ctad_kind == inherited) + { + processing_template_decl_sentinel ptds (/*reset*/false); + if (TREE_CODE (fprime) == TEMPLATE_DECL) + ++processing_template_decl; + + tree targs = type_targs_deducible_from (tmpl, ret); + if (!targs) continue; + + if (TREE_CODE (f) != TEMPLATE_DECL) + fprime = copy_decl (fprime); + tree fntype = TREE_TYPE (fprime); + ret = lookup_template_class (TPARMS_PRIMARY_TEMPLATE (atparms), targs, + in_decl, NULL_TREE, false, complain); + fntype = build_function_type (ret, TYPE_ARG_TYPES (fntype)); + TREE_TYPE (fprime) = fntype; + if (TREE_CODE (fprime) == TEMPLATE_DECL) + TREE_TYPE (DECL_TEMPLATE_RESULT (fprime)) = fntype; } aguides = lookup_add (fprime, aguides); @@ -30035,33 +30090,92 @@ alias_ctad_tweaks (tree tmpl, tree uguides) return aguides; } -/* True iff template arguments for TMPL can be deduced from TYPE. +/* CTOR is a using-declaration inheriting the constructors of some base of the + class template TMPL; adjust the base's guides be deduction guides for TMPL. */ + +static tree +inherited_ctad_tweaks (tree tmpl, tree ctor, tsubst_flags_t complain) +{ + /* [over.match.class.deduct]: In addition, if C is defined and inherits + constructors ([namespace.udecl]) from a direct base class denoted in the + base-specifier-list by a class-or-decltype B, let A be an alias template + whose template parameter list is that of C and whose defining-type-id is + B. If A is a deducible template ([dcl.type.simple]), the set contains the + guides of A with the return type R of each guide replaced with typename + CC::type given a class template + + template <typename> class CC; + + whose primary template is not defined and with a single partial + specialization whose template parameter list is that of A and whose + template argument list is a specialization of A with the template argument + list of A ([temp.dep.type]) having a member typedef type designating a + template specialization with the template argument list of A but with C as + the template. */ + + /* FIXME: Also recognize inherited constructors of the form 'using C::B::B', + which seem to be represented with TYPENAME_TYPE C::B as USING_DECL_SCOPE? + And recognize constructors inherited from a non-dependent base class, which + seem to be missing from the overload set entirely? */ + tree scope = USING_DECL_SCOPE (ctor); + if (!CLASS_TYPE_P (scope) + || !CLASSTYPE_TEMPLATE_INFO (scope)) + return NULL_TREE; + + tree t = build_tree_list (DECL_TEMPLATE_PARMS (tmpl), scope); + bool any_dguides_p; + tree uguides = deduction_guides_for (TYPE_TI_TEMPLATE (scope), + any_dguides_p, complain); + return alias_ctad_tweaks (t, uguides); +} + +/* If template arguments for TMPL can be deduced from TYPE, return + the deduced arguments, otherwise return NULL_TREE. 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 +tree type_targs_deducible_from (tree tmpl, tree type) { - tree tparms = DECL_INNERMOST_TEMPLATE_PARMS (tmpl); + tree tparms, ttype; + if (TREE_CODE (tmpl) == TEMPLATE_DECL) + { + /* 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)) + { + if (CLASS_TYPE_P (type) + && CLASSTYPE_TEMPLATE_INFO (type) + && CLASSTYPE_TI_TEMPLATE (type) == tmpl) + return INNERMOST_TEMPLATE_ARGS (CLASSTYPE_TI_ARGS (type)); + else + return NULL_TREE; + } + + /* Otherwise it's an alias template. */ + tparms = DECL_INNERMOST_TEMPLATE_PARMS (tmpl); + ttype = TREE_TYPE (tmpl); + } + else + { + /* TMPL is a synthetic alias template represented as a TREE_LIST as + per alias_ctad_tweaks. */ + tparms = INNERMOST_TEMPLATE_PARMS (TREE_PURPOSE (tmpl)); + ttype = TREE_VALUE (tmpl); + tmpl = TI_TEMPLATE (TYPE_TEMPLATE_INFO_MAYBE_ALIAS (ttype)); + } + 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, + if (unify (tparms, targs, ttype, type, UNIFY_ALLOW_NONE, false)) - return false; + return NULL_TREE; /* We don't fail on an undeduced targ the second time through (like get_partial_spec_bindings) because we're going to try defaults. */ @@ -30074,7 +30188,7 @@ type_targs_deducible_from (tree tmpl, tree type) if (!tried_array_deduction && TREE_CODE (tparm) == TYPE_DECL) { - try_array_deduction (tparms, targs, TREE_TYPE (tmpl)); + try_array_deduction (tparms, targs, ttype); tried_array_deduction = true; if (TREE_VEC_ELT (targs, i)) goto again; @@ -30103,12 +30217,15 @@ type_targs_deducible_from (tree tmpl, tree type) partial specialization can't have default targs. */ targs = coerce_template_parms (tparms, targs, tmpl, tf_none); if (targs == error_mark_node) - return false; + return NULL_TREE; /* 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); + if (!constraints_satisfied_p (tmpl, targs)) + return NULL_TREE; + + return targs; } /* Return artificial deduction guides built from the constructors of class @@ -30124,7 +30241,7 @@ ctor_deduction_guides_for (tree tmpl, tsubst_flags_t complain) for (ovl_iterator iter (CLASSTYPE_CONSTRUCTORS (type)); iter; ++iter) { - /* Skip inherited constructors. */ + /* We handle C++23 inherited CTAD below. */ if (iter.using_p ()) continue; @@ -30132,6 +30249,15 @@ ctor_deduction_guides_for (tree tmpl, tsubst_flags_t complain) cands = lookup_add (guide, cands); } + if (cxx_dialect >= cxx23) + for (tree ctor : ovl_range (CLASSTYPE_CONSTRUCTORS (type))) + if (TREE_CODE (ctor) == USING_DECL) + { + tree uguides = inherited_ctad_tweaks (tmpl, ctor, complain); + if (uguides) + cands = lookup_add (uguides, cands); + } + /* Add implicit default constructor deduction guide. */ if (!TYPE_HAS_USER_CONSTRUCTOR (type)) { @@ -30181,7 +30307,10 @@ deduction_guides_for (tree tmpl, bool &any_dguides_p, tsubst_flags_t complain) } /* Cache the deduction guides for a template. We also remember the result of - lookup, and rebuild everything if it changes; should be very rare. */ + lookup, and rebuild everything if it changes; should be very rare. + + FIXME: Also rebuild if this is a class template that inherits guides from a + base class, and lookup for the latter changed. */ tree_pair_p cache = NULL; if (tree_pair_p &r = hash_map_safe_get_or_insert<hm_ggc> (dguide_cache, tmpl)) diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C index 4624794c4b7..74f92325d7a 100644 --- a/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C @@ -1,5 +1,4 @@ -// Deduction from inherited constructors isn't supported yet, but we shouldn't -// crash. It may well be supported in C++23. +// Deduction from inherited constructors isn't supported before C++23. //{ dg-do compile { target c++17 } } @@ -17,5 +16,5 @@ int main() { B b = 42; // { dg-line init } // { dg-prune-output "no matching function" } - // { dg-error "class template argument deduction" "" { target *-*-* } init } + // { dg-error "class template argument deduction" "" { target c++23_down } init } } diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C new file mode 100644 index 00000000000..5fd1270e819 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C @@ -0,0 +1,38 @@ +// Modified example from P2582R1 +// { dg-do compile { target c++23 } } + +template <typename T> struct B { + B(T); +}; +B(bool) -> B<char>; +template <typename T> struct C : public B<T> { + using B<T>::B; +}; +template <typename T> struct D : public B<T> {}; + +C c(42); // OK, deduces C<int> +using ty1 = decltype(c); +using ty1 = C<int>; + +D d(42); // { dg-error "deduction|no match" } + +C c2(true); // OK, deduces C<char> +using ty2 = decltype(c2); +using ty2 = C<char>; + +template <typename T> struct E : public B<int> { + using B<int>::B; +}; + +E e(42); // { dg-error "deduction|no match" } + +template <typename T, typename U, typename V> struct F { + F(T, U, V); +}; +template <typename T, typename U> struct G : F<U, T, int> { + using F<U, T, int>::F; +}; + +G g(true, 'a', 1); // OK, deduces G<char, bool> +using ty3 = decltype(g); +using ty3 = G<char, bool>; diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C new file mode 100644 index 00000000000..cb3c595f316 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C @@ -0,0 +1,26 @@ +// { dg-do compile { target c++23 } } + +template<class T, class U, class V> struct F { + F(T, U, V); // #1 + F(T*, U*, V*); // #2 + template<class W> + F(int, int, W); // #3 +}; + +F(bool, bool, bool) -> F<bool*, void*, int>; + +template<class T, class U> struct G : F<U, T, int> { + using F<U, T, int>::F; +}; + +using ty1 = decltype(G(true, 'a', 1)); // uses #1 +using ty1 = G<char, bool>; + +using ty2 = decltype(G((bool*)0, (char*)0, (int*)0)); // uses #2 +using ty2 = G<char, bool>; + +using ty3 = decltype(G(0, 0, 0)); // uses #3 +using ty3 = G<int, int>; + +using ty4 = decltype(G(true, true, true)); // uses #4 +using ty4 = G<void*, bool*>; diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C new file mode 100644 index 00000000000..57e323b5124 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C @@ -0,0 +1,16 @@ +// { dg-do compile { target c++23 } } + +template<class T> +struct A { + A(T); + template<class U> A(T, U); +}; + +template<class T> +struct B : A<const T> { + using A<const T>::A; +}; + +using type = decltype(B(0)); +using type = decltype(B(0, 0)); +using type = B<int>; diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C new file mode 100644 index 00000000000..5e3a7f42919 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C @@ -0,0 +1,32 @@ +// { dg-do compile { target c++23 } } + +template<class T> +struct A { A(T); }; + +template<class T> +struct B : A<T> { + using B::A::A; // FIXME: we don't notice this inherited ctor +}; + +using ty1 = decltype(B(0)); // { dg-bogus "" "" { xfail *-*-* } } +using ty1 = B<int>; + +template<class T=void> +struct C : A<int> { + using A<int>::A; // FIXME: we don't notice this one either +}; + +using ty2 = decltype(C(0)); // { dg-bogus "" "" { xfail *-*-* } } +using ty2 = C<void>; + +template<class T> +struct D : A<T> { + using A<T>::A; +}; + +using ty3 = decltype(D(0)); +using ty3 = D<int>; + +A(int) -> A<char>; // FIXME: we need to rebuild the guides of D +using ty4 = decltype(D(0)); +using ty4 = D<char>; // { dg-bogus "conflicting" "" { xfail *-*-* } }
On 11/27/23 10:58, Patrick Palka wrote: > gcc/cp/ChangeLog: > > * cp-tree.h (type_targs_deducible_from): Adjust return type. > * pt.cc (alias_ctad_tweaks): Handle C++23 inherited CTAD. > (inherited_ctad_tweaks): Define. > (type_targs_deducible_from): Return the deduced arguments or > NULL_TREE instead of a bool. Handle 'tmpl' being a TREE_LIST > representing a synthetic alias template. > (ctor_deduction_guides_for): Do inherited_ctad_tweaks for each > USING_DECL in C++23 mode. > (deduction_guides_for): Add FIXME for stale cache entries in > light of inherited CTAD. check_GNU_style.py notices a few too-long lines in comments: > === ERROR type #2: lines should not exceed 80 characters (3 error(s)) === > gcc/cp/pt.cc:30076:80: /* FIXME this should mean they don't compare as equivalent. */ > gcc/cp/pt.cc:30138:80: class template TMPL; adjust the base's guides be deduction guides for TMPL. */ > gcc/cp/pt.cc:30190:80: /* If tmpl is a class template, this is trivial: it's deducible if TYPE is a OK with those fixed. > gcc/testsuite/ChangeLog: > > * g++.dg/cpp1z/class-deduction67.C: Accept in C++23 mode. > * g++.dg/cpp23/class-deduction-inherited1.C: New test. > * g++.dg/cpp23/class-deduction-inherited2.C: New test. > * g++.dg/cpp23/class-deduction-inherited3.C: New test. > * g++.dg/cpp23/class-deduction-inherited4.C: New test. > --- > gcc/cp/cp-tree.h | 2 +- > gcc/cp/pt.cc | 187 +++++++++++++++--- > .../g++.dg/cpp1z/class-deduction67.C | 5 +- > .../g++.dg/cpp23/class-deduction-inherited1.C | 38 ++++ > .../g++.dg/cpp23/class-deduction-inherited2.C | 26 +++ > .../g++.dg/cpp23/class-deduction-inherited3.C | 16 ++ > .../g++.dg/cpp23/class-deduction-inherited4.C | 32 +++ > 7 files changed, 273 insertions(+), 33 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C > create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C > create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C > create mode 100644 gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h > index 7b0b7c6a17e..abc467fb290 100644 > --- a/gcc/cp/cp-tree.h > +++ b/gcc/cp/cp-tree.h > @@ -7457,7 +7457,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 tree 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/pt.cc b/gcc/cp/pt.cc > index 092e6fdfd36..5b982d1d641 100644 > --- a/gcc/cp/pt.cc > +++ b/gcc/cp/pt.cc > @@ -223,6 +223,9 @@ static void instantiate_body (tree pattern, tree args, tree d, bool nested); > static tree maybe_dependent_member_ref (tree, tree, tsubst_flags_t, tree); > static void mark_template_arguments_used (tree, tree); > static bool uses_outer_template_parms (tree); > +static tree alias_ctad_tweaks (tree, tree); > +static tree inherited_ctad_tweaks (tree, tree, tsubst_flags_t); > +static tree deduction_guides_for (tree, bool&, tsubst_flags_t); > > /* Make the current scope suitable for access checking when we are > processing T. T can be FUNCTION_DECL for instantiated function > @@ -29736,8 +29739,6 @@ is_spec_or_derived (tree etype, tree tmpl) > return !err; > } > > -static tree alias_ctad_tweaks (tree, tree); > - > /* Return a C++20 aggregate deduction candidate for TYPE initialized from > INIT. */ > > @@ -29842,7 +29843,13 @@ maybe_aggr_guide (tree tmpl, tree init, vec<tree,va_gc> *args) > } > > /* UGUIDES are the deduction guides for the underlying template of alias > - template TMPL; adjust them to be deduction guides for TMPL. */ > + template TMPL; adjust them to be deduction guides for TMPL. > + > + This routine also handles C++23 inherited CTAD, in which case TMPL is a > + TREE_LIST representing a synthetic alias template whose TREE_PURPOSE is > + the template parameter list of the alias template (equivalently, of the > + derived class) and TREE_VALUE the defining-type-id (equivalently, the > + base whose guides we're inheriting). UGUIDES are the base's guides. */ > > static tree > alias_ctad_tweaks (tree tmpl, tree uguides) > @@ -29886,13 +29893,27 @@ alias_ctad_tweaks (tree tmpl, tree uguides) > * The explicit-specifier of f' is the explicit-specifier of g (if > any). */ > > + enum { alias, inherited } ctad_kind; > + tree atype, fullatparms, utype; > + if (TREE_CODE (tmpl) == TEMPLATE_DECL) > + { > + ctad_kind = alias; > + atype = TREE_TYPE (tmpl); > + fullatparms = DECL_TEMPLATE_PARMS (tmpl); > + utype = DECL_ORIGINAL_TYPE (DECL_TEMPLATE_RESULT (tmpl)); > + } > + else > + { > + ctad_kind = inherited; > + atype = NULL_TREE; > + fullatparms = TREE_PURPOSE (tmpl); > + utype = TREE_VALUE (tmpl); > + } > + > tsubst_flags_t complain = tf_warning_or_error; > - tree atype = TREE_TYPE (tmpl); > tree aguides = NULL_TREE; > - tree fullatparms = DECL_TEMPLATE_PARMS (tmpl); > tree atparms = INNERMOST_TEMPLATE_PARMS (fullatparms); > unsigned natparms = TREE_VEC_LENGTH (atparms); > - tree utype = DECL_ORIGINAL_TYPE (DECL_TEMPLATE_RESULT (tmpl)); > for (ovl_iterator iter (uguides); iter; ++iter) > { > tree f = *iter; > @@ -29930,7 +29951,7 @@ alias_ctad_tweaks (tree tmpl, tree uguides) > > /* Set current_template_parms as in build_deduction_guide. */ > auto ctp = make_temp_override (current_template_parms); > - current_template_parms = copy_node (DECL_TEMPLATE_PARMS (tmpl)); > + current_template_parms = copy_node (fullatparms); > TREE_VALUE (current_template_parms) = gtparms; > > j = 0; > @@ -30006,9 +30027,10 @@ alias_ctad_tweaks (tree tmpl, tree uguides) > /* Add a constraint that the return type matches the instantiation of > A with the same template arguments. */ > ret = TREE_TYPE (TREE_TYPE (fprime)); > - if (!same_type_p (atype, ret) > - /* FIXME this should mean they don't compare as equivalent. */ > - || dependent_alias_template_spec_p (atype, nt_opaque)) > + if (ctad_kind == alias > + && (!same_type_p (atype, ret) > + /* 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_DEDUCIBLE, tmpl, ret); > ci = append_constraint (ci, same); > @@ -30025,8 +30047,41 @@ 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. */ > non_template: > - if (!type_targs_deducible_from (tmpl, ret)) > + if (ctad_kind == alias > + && !type_targs_deducible_from (tmpl, ret)) > + continue; > + } > + > + /* Rewrite the return type of the inherited guide in terms of the > + derived class. This is specified as replacing the return type R > + with typename CC<R>::type where the partially specialized CC maps a > + base class specialization to a specialization of the derived class > + having such a base (inducing substitution failure if no such derived > + class exists). > + > + As specified this mapping would be done at instantiation time using > + non-dependent template arguments, but we do it ahead of time using > + the generic arguments. This seems to be good enough since generic > + deduction should succeed only if concrete deduction would. */ > + if (ctad_kind == inherited) > + { > + processing_template_decl_sentinel ptds (/*reset*/false); > + if (TREE_CODE (fprime) == TEMPLATE_DECL) > + ++processing_template_decl; > + > + tree targs = type_targs_deducible_from (tmpl, ret); > + if (!targs) > continue; > + > + if (TREE_CODE (f) != TEMPLATE_DECL) > + fprime = copy_decl (fprime); > + tree fntype = TREE_TYPE (fprime); > + ret = lookup_template_class (TPARMS_PRIMARY_TEMPLATE (atparms), targs, > + in_decl, NULL_TREE, false, complain); > + fntype = build_function_type (ret, TYPE_ARG_TYPES (fntype)); > + TREE_TYPE (fprime) = fntype; > + if (TREE_CODE (fprime) == TEMPLATE_DECL) > + TREE_TYPE (DECL_TEMPLATE_RESULT (fprime)) = fntype; > } > > aguides = lookup_add (fprime, aguides); > @@ -30035,33 +30090,92 @@ alias_ctad_tweaks (tree tmpl, tree uguides) > return aguides; > } > > -/* True iff template arguments for TMPL can be deduced from TYPE. > +/* CTOR is a using-declaration inheriting the constructors of some base of the > + class template TMPL; adjust the base's guides be deduction guides for TMPL. */ > + > +static tree > +inherited_ctad_tweaks (tree tmpl, tree ctor, tsubst_flags_t complain) > +{ > + /* [over.match.class.deduct]: In addition, if C is defined and inherits > + constructors ([namespace.udecl]) from a direct base class denoted in the > + base-specifier-list by a class-or-decltype B, let A be an alias template > + whose template parameter list is that of C and whose defining-type-id is > + B. If A is a deducible template ([dcl.type.simple]), the set contains the > + guides of A with the return type R of each guide replaced with typename > + CC::type given a class template > + > + template <typename> class CC; > + > + whose primary template is not defined and with a single partial > + specialization whose template parameter list is that of A and whose > + template argument list is a specialization of A with the template argument > + list of A ([temp.dep.type]) having a member typedef type designating a > + template specialization with the template argument list of A but with C as > + the template. */ > + > + /* FIXME: Also recognize inherited constructors of the form 'using C::B::B', > + which seem to be represented with TYPENAME_TYPE C::B as USING_DECL_SCOPE? > + And recognize constructors inherited from a non-dependent base class, which > + seem to be missing from the overload set entirely? */ > + tree scope = USING_DECL_SCOPE (ctor); > + if (!CLASS_TYPE_P (scope) > + || !CLASSTYPE_TEMPLATE_INFO (scope)) > + return NULL_TREE; > + > + tree t = build_tree_list (DECL_TEMPLATE_PARMS (tmpl), scope); > + bool any_dguides_p; > + tree uguides = deduction_guides_for (TYPE_TI_TEMPLATE (scope), > + any_dguides_p, complain); > + return alias_ctad_tweaks (t, uguides); > +} > + > +/* If template arguments for TMPL can be deduced from TYPE, return > + the deduced arguments, otherwise return NULL_TREE. > 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 > +tree > type_targs_deducible_from (tree tmpl, tree type) > { > - tree tparms = DECL_INNERMOST_TEMPLATE_PARMS (tmpl); > + tree tparms, ttype; > + if (TREE_CODE (tmpl) == TEMPLATE_DECL) > + { > + /* 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)) > + { > + if (CLASS_TYPE_P (type) > + && CLASSTYPE_TEMPLATE_INFO (type) > + && CLASSTYPE_TI_TEMPLATE (type) == tmpl) > + return INNERMOST_TEMPLATE_ARGS (CLASSTYPE_TI_ARGS (type)); > + else > + return NULL_TREE; > + } > + > + /* Otherwise it's an alias template. */ > + tparms = DECL_INNERMOST_TEMPLATE_PARMS (tmpl); > + ttype = TREE_TYPE (tmpl); > + } > + else > + { > + /* TMPL is a synthetic alias template represented as a TREE_LIST as > + per alias_ctad_tweaks. */ > + tparms = INNERMOST_TEMPLATE_PARMS (TREE_PURPOSE (tmpl)); > + ttype = TREE_VALUE (tmpl); > + tmpl = TI_TEMPLATE (TYPE_TEMPLATE_INFO_MAYBE_ALIAS (ttype)); > + } > + > 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, > + if (unify (tparms, targs, ttype, type, > UNIFY_ALLOW_NONE, false)) > - return false; > + return NULL_TREE; > > /* We don't fail on an undeduced targ the second time through (like > get_partial_spec_bindings) because we're going to try defaults. */ > @@ -30074,7 +30188,7 @@ type_targs_deducible_from (tree tmpl, tree type) > if (!tried_array_deduction > && TREE_CODE (tparm) == TYPE_DECL) > { > - try_array_deduction (tparms, targs, TREE_TYPE (tmpl)); > + try_array_deduction (tparms, targs, ttype); > tried_array_deduction = true; > if (TREE_VEC_ELT (targs, i)) > goto again; > @@ -30103,12 +30217,15 @@ type_targs_deducible_from (tree tmpl, tree type) > partial specialization can't have default targs. */ > targs = coerce_template_parms (tparms, targs, tmpl, tf_none); > if (targs == error_mark_node) > - return false; > + return NULL_TREE; > > /* 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); > + if (!constraints_satisfied_p (tmpl, targs)) > + return NULL_TREE; > + > + return targs; > } > > /* Return artificial deduction guides built from the constructors of class > @@ -30124,7 +30241,7 @@ ctor_deduction_guides_for (tree tmpl, tsubst_flags_t complain) > > for (ovl_iterator iter (CLASSTYPE_CONSTRUCTORS (type)); iter; ++iter) > { > - /* Skip inherited constructors. */ > + /* We handle C++23 inherited CTAD below. */ > if (iter.using_p ()) > continue; > > @@ -30132,6 +30249,15 @@ ctor_deduction_guides_for (tree tmpl, tsubst_flags_t complain) > cands = lookup_add (guide, cands); > } > > + if (cxx_dialect >= cxx23) > + for (tree ctor : ovl_range (CLASSTYPE_CONSTRUCTORS (type))) > + if (TREE_CODE (ctor) == USING_DECL) > + { > + tree uguides = inherited_ctad_tweaks (tmpl, ctor, complain); > + if (uguides) > + cands = lookup_add (uguides, cands); > + } > + > /* Add implicit default constructor deduction guide. */ > if (!TYPE_HAS_USER_CONSTRUCTOR (type)) > { > @@ -30181,7 +30307,10 @@ deduction_guides_for (tree tmpl, bool &any_dguides_p, tsubst_flags_t complain) > } > > /* Cache the deduction guides for a template. We also remember the result of > - lookup, and rebuild everything if it changes; should be very rare. */ > + lookup, and rebuild everything if it changes; should be very rare. > + > + FIXME: Also rebuild if this is a class template that inherits guides from a > + base class, and lookup for the latter changed. */ > tree_pair_p cache = NULL; > if (tree_pair_p &r > = hash_map_safe_get_or_insert<hm_ggc> (dguide_cache, tmpl)) > diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C > index 4624794c4b7..74f92325d7a 100644 > --- a/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C > @@ -1,5 +1,4 @@ > -// Deduction from inherited constructors isn't supported yet, but we shouldn't > -// crash. It may well be supported in C++23. > +// Deduction from inherited constructors isn't supported before C++23. > > //{ dg-do compile { target c++17 } } > > @@ -17,5 +16,5 @@ int main() > { > B b = 42; // { dg-line init } > // { dg-prune-output "no matching function" } > - // { dg-error "class template argument deduction" "" { target *-*-* } init } > + // { dg-error "class template argument deduction" "" { target c++23_down } init } > } > diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C > new file mode 100644 > index 00000000000..5fd1270e819 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C > @@ -0,0 +1,38 @@ > +// Modified example from P2582R1 > +// { dg-do compile { target c++23 } } > + > +template <typename T> struct B { > + B(T); > +}; > +B(bool) -> B<char>; > +template <typename T> struct C : public B<T> { > + using B<T>::B; > +}; > +template <typename T> struct D : public B<T> {}; > + > +C c(42); // OK, deduces C<int> > +using ty1 = decltype(c); > +using ty1 = C<int>; > + > +D d(42); // { dg-error "deduction|no match" } > + > +C c2(true); // OK, deduces C<char> > +using ty2 = decltype(c2); > +using ty2 = C<char>; > + > +template <typename T> struct E : public B<int> { > + using B<int>::B; > +}; > + > +E e(42); // { dg-error "deduction|no match" } > + > +template <typename T, typename U, typename V> struct F { > + F(T, U, V); > +}; > +template <typename T, typename U> struct G : F<U, T, int> { > + using F<U, T, int>::F; > +}; > + > +G g(true, 'a', 1); // OK, deduces G<char, bool> > +using ty3 = decltype(g); > +using ty3 = G<char, bool>; > diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C > new file mode 100644 > index 00000000000..cb3c595f316 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C > @@ -0,0 +1,26 @@ > +// { dg-do compile { target c++23 } } > + > +template<class T, class U, class V> struct F { > + F(T, U, V); // #1 > + F(T*, U*, V*); // #2 > + template<class W> > + F(int, int, W); // #3 > +}; > + > +F(bool, bool, bool) -> F<bool*, void*, int>; > + > +template<class T, class U> struct G : F<U, T, int> { > + using F<U, T, int>::F; > +}; > + > +using ty1 = decltype(G(true, 'a', 1)); // uses #1 > +using ty1 = G<char, bool>; > + > +using ty2 = decltype(G((bool*)0, (char*)0, (int*)0)); // uses #2 > +using ty2 = G<char, bool>; > + > +using ty3 = decltype(G(0, 0, 0)); // uses #3 > +using ty3 = G<int, int>; > + > +using ty4 = decltype(G(true, true, true)); // uses #4 > +using ty4 = G<void*, bool*>; > diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C > new file mode 100644 > index 00000000000..57e323b5124 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C > @@ -0,0 +1,16 @@ > +// { dg-do compile { target c++23 } } > + > +template<class T> > +struct A { > + A(T); > + template<class U> A(T, U); > +}; > + > +template<class T> > +struct B : A<const T> { > + using A<const T>::A; > +}; > + > +using type = decltype(B(0)); > +using type = decltype(B(0, 0)); > +using type = B<int>; > diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C > new file mode 100644 > index 00000000000..5e3a7f42919 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited4.C > @@ -0,0 +1,32 @@ > +// { dg-do compile { target c++23 } } > + > +template<class T> > +struct A { A(T); }; > + > +template<class T> > +struct B : A<T> { > + using B::A::A; // FIXME: we don't notice this inherited ctor > +}; > + > +using ty1 = decltype(B(0)); // { dg-bogus "" "" { xfail *-*-* } } > +using ty1 = B<int>; > + > +template<class T=void> > +struct C : A<int> { > + using A<int>::A; // FIXME: we don't notice this one either > +}; > + > +using ty2 = decltype(C(0)); // { dg-bogus "" "" { xfail *-*-* } } > +using ty2 = C<void>; > + > +template<class T> > +struct D : A<T> { > + using A<T>::A; > +}; > + > +using ty3 = decltype(D(0)); > +using ty3 = D<int>; > + > +A(int) -> A<char>; // FIXME: we need to rebuild the guides of D > +using ty4 = decltype(D(0)); > +using ty4 = D<char>; // { dg-bogus "conflicting" "" { xfail *-*-* } }
On Wed, Dec 13, 2023 at 08:48:49PM -0500, Jason Merrill wrote: > On 11/27/23 10:58, Patrick Palka wrote: > > gcc/cp/ChangeLog: > > > > * cp-tree.h (type_targs_deducible_from): Adjust return type. > > * pt.cc (alias_ctad_tweaks): Handle C++23 inherited CTAD. > > (inherited_ctad_tweaks): Define. > > (type_targs_deducible_from): Return the deduced arguments or > > NULL_TREE instead of a bool. Handle 'tmpl' being a TREE_LIST > > representing a synthetic alias template. > > (ctor_deduction_guides_for): Do inherited_ctad_tweaks for each > > USING_DECL in C++23 mode. > > (deduction_guides_for): Add FIXME for stale cache entries in > > light of inherited CTAD. > > check_GNU_style.py notices a few too-long lines in comments: > > > === ERROR type #2: lines should not exceed 80 characters (3 error(s)) === > > gcc/cp/pt.cc:30076:80: /* FIXME this should mean they don't compare as equivalent. */ > > gcc/cp/pt.cc:30138:80: class template TMPL; adjust the base's guides be deduction guides for TMPL. */ > > gcc/cp/pt.cc:30190:80: /* If tmpl is a class template, this is trivial: it's deducible if TYPE is a > > OK with those fixed. > > index 4624794c4b7..74f92325d7a 100644 > > --- a/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C > > +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C > > @@ -1,5 +1,4 @@ > > -// Deduction from inherited constructors isn't supported yet, but we shouldn't > > -// crash. It may well be supported in C++23. > > +// Deduction from inherited constructors isn't supported before C++23. > > //{ dg-do compile { target c++17 } } > > @@ -17,5 +16,5 @@ int main() > > { > > B b = 42; // { dg-line init } > > // { dg-prune-output "no matching function" } > > - // { dg-error "class template argument deduction" "" { target *-*-* } init } > > + // { dg-error "class template argument deduction" "" { target c++23_down } init } > > } I checked in this patch: -- >8 -- The test says that CTAD from inherited constructors doesn't work before C++23 so we should use c++20_down for the error. gcc/testsuite/ChangeLog: * g++.dg/cpp1z/class-deduction67.C: Correct dg-error target. --- gcc/testsuite/g++.dg/cpp1z/class-deduction67.C | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C index 74f92325d7a..fa1523d99d5 100644 --- a/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C @@ -16,5 +16,5 @@ int main() { B b = 42; // { dg-line init } // { dg-prune-output "no matching function" } - // { dg-error "class template argument deduction" "" { target c++23_down } init } + // { dg-error "class template argument deduction" "" { target c++20_down } init } } base-commit: e5e1999aa664333f766f3e6cc6996f769d50ae7a
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 1fa710d7154..633d58b1d12 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -7434,7 +7434,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 bool type_targs_deducible_from (tree, tree, tree * = nullptr); 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/pt.cc b/gcc/cp/pt.cc index 324f6f01555..75f5bc9bed5 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -223,6 +223,9 @@ static void instantiate_body (tree pattern, tree args, tree d, bool nested); static tree maybe_dependent_member_ref (tree, tree, tsubst_flags_t, tree); static void mark_template_arguments_used (tree, tree); static bool uses_outer_template_parms (tree); +static tree alias_ctad_tweaks (tree, tree); +static tree inherited_ctad_tweaks (tree, tree, tsubst_flags_t); +static tree deduction_guides_for (tree, bool&, tsubst_flags_t); /* Make the current scope suitable for access checking when we are processing T. T can be FUNCTION_DECL for instantiated function @@ -29753,8 +29756,6 @@ is_spec_or_derived (tree etype, tree tmpl) return !err; } -static tree alias_ctad_tweaks (tree, tree); - /* Return a C++20 aggregate deduction candidate for TYPE initialized from INIT. */ @@ -29859,7 +29860,13 @@ maybe_aggr_guide (tree tmpl, tree init, vec<tree,va_gc> *args) } /* UGUIDES are the deduction guides for the underlying template of alias - template TMPL; adjust them to be deduction guides for TMPL. */ + template TMPL; adjust them to be deduction guides for TMPL. + + This routine also handles C++23 inherited CTAD, in which case TMPL is a + TREE_LIST representing a synthetic alias template whose TREE_PURPOSE is + the template parameter list of the alias template (equivalently, of the + derived class) and TREE_VALUE the defining-type-id (equivalently, the + base whose guides we're inheriting). UGUIDES are the base's guides. */ static tree alias_ctad_tweaks (tree tmpl, tree uguides) @@ -29903,13 +29910,30 @@ alias_ctad_tweaks (tree tmpl, tree uguides) * The explicit-specifier of f' is the explicit-specifier of g (if any). */ + enum { alias, inherited } ctad_kind; + + tree atype; + tree fullatparms; + tree utype; + if (TREE_CODE (tmpl) == TEMPLATE_DECL) + { + ctad_kind = alias; + atype = TREE_TYPE (tmpl); + fullatparms = DECL_TEMPLATE_PARMS (tmpl); + utype = DECL_ORIGINAL_TYPE (DECL_TEMPLATE_RESULT (tmpl)); + } + else + { + ctad_kind = inherited; + atype = NULL_TREE; + fullatparms = TREE_PURPOSE (tmpl); + utype = TREE_VALUE (tmpl); + } + tsubst_flags_t complain = tf_warning_or_error; - tree atype = TREE_TYPE (tmpl); tree aguides = NULL_TREE; - tree fullatparms = DECL_TEMPLATE_PARMS (tmpl); tree atparms = INNERMOST_TEMPLATE_PARMS (fullatparms); unsigned natparms = TREE_VEC_LENGTH (atparms); - tree utype = DECL_ORIGINAL_TYPE (DECL_TEMPLATE_RESULT (tmpl)); for (ovl_iterator iter (uguides); iter; ++iter) { tree f = *iter; @@ -29947,7 +29971,7 @@ alias_ctad_tweaks (tree tmpl, tree uguides) /* Set current_template_parms as in build_deduction_guide. */ auto ctp = make_temp_override (current_template_parms); - current_template_parms = copy_node (DECL_TEMPLATE_PARMS (tmpl)); + current_template_parms = copy_node (fullatparms); TREE_VALUE (current_template_parms) = gtparms; j = 0; @@ -30023,9 +30047,10 @@ alias_ctad_tweaks (tree tmpl, tree uguides) /* Add a constraint that the return type matches the instantiation of A with the same template arguments. */ ret = TREE_TYPE (TREE_TYPE (fprime)); - if (!same_type_p (atype, ret) - /* FIXME this should mean they don't compare as equivalent. */ - || dependent_alias_template_spec_p (atype, nt_opaque)) + if (ctad_kind == alias + && (!same_type_p (atype, ret) + /* 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_DEDUCIBLE, tmpl, ret); ci = append_constraint (ci, same); @@ -30042,8 +30067,37 @@ 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. */ non_template: - if (!type_targs_deducible_from (tmpl, ret)) + if (ctad_kind == alias + && !type_targs_deducible_from (tmpl, ret)) + continue; + } + + /* Rewrite the return type of the inherited guide in terms of the + derived class. This is specified as replacing the return type R + with typename CC<R>::type where the partially specialized CC maps a + base class specialization to a specialization of the derived class + having such a base (inducing substitution failure if no such derived + class exists). + + As specified this mapping would be done at instantiation time using + non-dependent template arguments, but we do it ahead of time using + the generic arguments. This seems to be good enough since generic + deduction should succeed only if concrete deduction would. */ + if (ctad_kind == inherited) + { + tree targs; + if (!type_targs_deducible_from (tmpl, ret, &targs)) continue; + + if (TREE_CODE (f) != TEMPLATE_DECL) + fprime = copy_decl (fprime); + tree fntype = TREE_TYPE (fprime); + ret = lookup_template_class (TPARMS_PRIMARY_TEMPLATE (atparms), targs, + in_decl, NULL_TREE, false, complain); + fntype = build_function_type (ret, TYPE_ARG_TYPES (fntype)); + TREE_TYPE (fprime) = fntype; + if (TREE_CODE (fprime) == TEMPLATE_DECL) + TREE_TYPE (DECL_TEMPLATE_RESULT (fprime)) = fntype; } aguides = lookup_add (fprime, aguides); @@ -30052,6 +30106,45 @@ alias_ctad_tweaks (tree tmpl, tree uguides) return aguides; } +/* CTOR is a using-declaration inheriting the constructors of some base of the + class template TMPL; adjust the base's guides be deduction guides for TMPL. */ + +static tree +inherited_ctad_tweaks (tree tmpl, tree ctor, tsubst_flags_t complain) +{ + /* [over.match.class.deduct]: In addition, if C is defined and inherits + constructors ([namespace.udecl]) from a direct base class denoted in the + base-specifier-list by a class-or-decltype B, let A be an alias template + whose template parameter list is that of C and whose defining-type-id is + B. If A is a deducible template ([dcl.type.simple]), the set contains the + guides of A with the return type R of each guide replaced with typename + CC::type given a class template + + template <typename> class CC; + + whose primary template is not defined and with a single partial + specialization whose template parameter list is that of A and whose + template argument list is a specialization of A with the template argument + list of A ([temp.dep.type]) having a member typedef type designating a + template specialization with the template argument list of A but with C as + the template. */ + + /* FIXME: Also recognize inherited constructors of the form 'using C::B::B', + which seem to be represented with TYPENAME_TYPE C::B as USING_DECL_SCOPE? + And recognize constructors inherited from a non-dependent base class, which + seem to be missing from the overload set entirely? */ + tree scope = USING_DECL_SCOPE (ctor); + if (!CLASS_TYPE_P (scope) + || !CLASSTYPE_TEMPLATE_INFO (scope)) + return NULL_TREE; + + tree t = build_tree_list (DECL_TEMPLATE_PARMS (tmpl), scope); + bool any_dguides_p; + tree uguides = deduction_guides_for (TYPE_TI_TEMPLATE (scope), + any_dguides_p, complain); + return alias_ctad_tweaks (t, uguides); +} + /* 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]. @@ -30060,23 +30153,37 @@ alias_ctad_tweaks (tree tmpl, tree uguides) should be parallel to that of get_partial_spec_bindings. */ bool -type_targs_deducible_from (tree tmpl, tree type) +type_targs_deducible_from (tree tmpl, tree type, tree *targs_out /* = nullptr */) { - tree tparms = DECL_INNERMOST_TEMPLATE_PARMS (tmpl); + tree tparms, ttype; + if (TREE_CODE (tmpl) == TEMPLATE_DECL) + { + /* 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. */ + tparms = DECL_INNERMOST_TEMPLATE_PARMS (tmpl); + ttype = TREE_TYPE (tmpl); + } + else + { + /* TMPL is a synthetic alias template represented as a TREE_LIST as + per alias_ctad_tweaks. */ + tparms = INNERMOST_TEMPLATE_PARMS (TREE_PURPOSE (tmpl)); + ttype = TREE_VALUE (tmpl); + tmpl = TI_TEMPLATE (TYPE_TEMPLATE_INFO_MAYBE_ALIAS (ttype)); + } + 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, + if (unify (tparms, targs, ttype, type, UNIFY_ALLOW_NONE, false)) return false; @@ -30091,7 +30198,7 @@ type_targs_deducible_from (tree tmpl, tree type) if (!tried_array_deduction && TREE_CODE (tparm) == TYPE_DECL) { - try_array_deduction (tparms, targs, TREE_TYPE (tmpl)); + try_array_deduction (tparms, targs, ttype); tried_array_deduction = true; if (TREE_VEC_ELT (targs, i)) goto again; @@ -30125,7 +30232,12 @@ type_targs_deducible_from (tree tmpl, tree type) /* 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); + if (!constraints_satisfied_p (tmpl, targs)) + return false; + + if (targs_out) + *targs_out = targs; + return true; } /* Return artificial deduction guides built from the constructors of class @@ -30141,7 +30253,7 @@ ctor_deduction_guides_for (tree tmpl, tsubst_flags_t complain) for (ovl_iterator iter (CLASSTYPE_CONSTRUCTORS (type)); iter; ++iter) { - /* Skip inherited constructors. */ + /* We handle C++23 inherited CTAD below. */ if (iter.using_p ()) continue; @@ -30149,6 +30261,15 @@ ctor_deduction_guides_for (tree tmpl, tsubst_flags_t complain) cands = lookup_add (guide, cands); } + if (cxx_dialect >= cxx23) + for (tree ctor : ovl_range (CLASSTYPE_CONSTRUCTORS (type))) + if (TREE_CODE (ctor) == USING_DECL) + { + tree uguides = inherited_ctad_tweaks (tmpl, ctor, complain); + if (uguides) + cands = lookup_add (uguides, cands); + } + /* Add implicit default constructor deduction guide. */ if (!TYPE_HAS_USER_CONSTRUCTOR (type)) { @@ -30198,7 +30319,10 @@ deduction_guides_for (tree tmpl, bool &any_dguides_p, tsubst_flags_t complain) } /* Cache the deduction guides for a template. We also remember the result of - lookup, and rebuild everything if it changes; should be very rare. */ + lookup, and rebuild everything if it changes; should be very rare. + + FIXME: Also rebuild if this is a class template that inherits guides from a + base class, and lookup for the latter changed. */ tree_pair_p cache = NULL; if (tree_pair_p &r = hash_map_safe_get_or_insert<hm_ggc> (dguide_cache, tmpl)) diff --git a/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C b/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C index 4624794c4b7..74f92325d7a 100644 --- a/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C +++ b/gcc/testsuite/g++.dg/cpp1z/class-deduction67.C @@ -1,5 +1,4 @@ -// Deduction from inherited constructors isn't supported yet, but we shouldn't -// crash. It may well be supported in C++23. +// Deduction from inherited constructors isn't supported before C++23. //{ dg-do compile { target c++17 } } @@ -17,5 +16,5 @@ int main() { B b = 42; // { dg-line init } // { dg-prune-output "no matching function" } - // { dg-error "class template argument deduction" "" { target *-*-* } init } + // { dg-error "class template argument deduction" "" { target c++23_down } init } } diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C new file mode 100644 index 00000000000..aa1948531e1 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited1.C @@ -0,0 +1,36 @@ +// Modified example from P2582R1 +// { dg-do compile { target c++23 } } + +template <typename T> struct B { + B(T); +}; +B(bool) -> B<char>; +template <typename T> struct C : public B<T> { + using B<T>::B; +}; +template <typename T> struct D : public B<T> {}; + +C c(42); // OK, deduces C<int> +using ty1 = decltype(C(42)); +using ty1 = C<int>; + +D d(42); // { dg-error "deduction|no match" } + +C c2(true); // OK, deduces C<char> +using ty2 = decltype(C(true)); +using ty2 = C<char>; + +template <typename T> struct E : public B<int> { + using B<int>::B; +}; + +E e(42); // { dg-error "deduction|no match" } + +template <typename T, typename U, typename V> struct F { + F(T, U, V); +}; +template <typename T, typename U> struct G : F<U, T, int> { + using F<U, T, int>::F; +}; + +G g(true, 'a', 1); // OK, deduces G<char, bool> diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C new file mode 100644 index 00000000000..cb3c595f316 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited2.C @@ -0,0 +1,26 @@ +// { dg-do compile { target c++23 } } + +template<class T, class U, class V> struct F { + F(T, U, V); // #1 + F(T*, U*, V*); // #2 + template<class W> + F(int, int, W); // #3 +}; + +F(bool, bool, bool) -> F<bool*, void*, int>; + +template<class T, class U> struct G : F<U, T, int> { + using F<U, T, int>::F; +}; + +using ty1 = decltype(G(true, 'a', 1)); // uses #1 +using ty1 = G<char, bool>; + +using ty2 = decltype(G((bool*)0, (char*)0, (int*)0)); // uses #2 +using ty2 = G<char, bool>; + +using ty3 = decltype(G(0, 0, 0)); // uses #3 +using ty3 = G<int, int>; + +using ty4 = decltype(G(true, true, true)); // uses #4 +using ty4 = G<void*, bool*>; diff --git a/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C new file mode 100644 index 00000000000..57e323b5124 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/class-deduction-inherited3.C @@ -0,0 +1,16 @@ +// { dg-do compile { target c++23 } } + +template<class T> +struct A { + A(T); + template<class U> A(T, U); +}; + +template<class T> +struct B : A<const T> { + using A<const T>::A; +}; + +using type = decltype(B(0)); +using type = decltype(B(0, 0)); +using type = B<int>;