On Thu, 8 May 2025, Jason Merrill wrote: > Tested x86_64-pc-linux-gnu, applying to trunk. > > -- 8< -- > > This tweak to CWG2369 has gotten more discussion lately in CWG, including in > P3606. In those discussions, it occurred to me that having the check depend > on whether a class has been instantiated yet is unstable, that it should > only check for user-defined conversions.
Do we want to explicitly complete_type in conversion_may_instantiate_p then? reference_related_p will sometimes do it for us, but only if both types are class types, so it seems to me the check still behaves differently in some cases if one of the types is not yet instantiated? > > Also, one commenter was surprised that adding an explicitly-declared default > constructor to a class changed things, so this patch also changes the > aggregate check to more narrowly checking for one-argument constructors > other than the copy/move constructors. > > As a result, this early filter resembles how LOOKUP_DEFAULTED rejects any > candidate that would need a UDC: in both cases we want to avoid considering > arbitrary UDCs. But here, rather than rejecting, we want the early filter > to let the candidate past without considering the conversion. > > PR c++/99599 > > gcc/cp/ChangeLog: > > * cp-tree.h (type_has_converting_constructor): Declare. > * class.cc (type_has_converting_constructor): New. > * pt.cc (conversion_may_instantiate_p): Don't check completeness. > > gcc/testsuite/ChangeLog: > > * g++.dg/cpp2a/concepts-recursive-sat4.C: Adjust again. > * g++.dg/cpp2a/concepts-nondep5.C: New test. > --- > gcc/cp/cp-tree.h | 1 + > gcc/cp/class.cc | 41 +++++++++++++++++++ > gcc/cp/pt.cc | 40 +++++------------- > gcc/testsuite/g++.dg/cpp2a/concepts-nondep5.C | 34 +++++++++++++++ > .../g++.dg/cpp2a/concepts-recursive-sat4.C | 2 +- > 5 files changed, 88 insertions(+), 30 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-nondep5.C > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h > index a42c07a330b..175ab287490 100644 > --- a/gcc/cp/cp-tree.h > +++ b/gcc/cp/cp-tree.h > @@ -7056,6 +7056,7 @@ extern tree in_class_defaulted_default_constructor > (tree); > extern bool user_provided_p (tree); > extern bool type_has_user_provided_constructor (tree); > extern bool type_has_non_user_provided_default_constructor (tree); > +extern bool type_has_converting_constructor (tree); > extern bool vbase_has_user_provided_move_assign (tree); > extern tree default_init_uninitialized_part (tree); > extern bool trivial_default_constructor_is_constexpr (tree); > diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc > index 6767ac10358..370bfa35f9e 100644 > --- a/gcc/cp/class.cc > +++ b/gcc/cp/class.cc > @@ -5724,6 +5724,47 @@ type_has_user_provided_constructor (tree t) > return false; > } > > +/* Returns true iff class T has a constructor that accepts a single argument > + and does not have a single parameter of type reference to T. > + > + This does not exclude explicit constructors because they are still > + considered for conversions within { } even though choosing one is > + ill-formed. */ > + > +bool > +type_has_converting_constructor (tree t) > +{ > + if (!CLASS_TYPE_P (t)) > + return false; > + > + if (!TYPE_HAS_USER_CONSTRUCTOR (t)) > + return false; > + > + for (ovl_iterator iter (CLASSTYPE_CONSTRUCTORS (t)); iter; ++iter) > + { > + tree fn = *iter; > + tree parm = FUNCTION_FIRST_USER_PARMTYPE (fn); > + if (parm == void_list_node > + || !sufficient_parms_p (TREE_CHAIN (parm))) > + /* Can't accept a single argument, so won't be considered for > + conversion. */ > + continue; > + if (TREE_CODE (fn) == TEMPLATE_DECL > + || TREE_CHAIN (parm) != void_list_node) > + /* Not a simple single parameter. */ > + return true; > + if (TYPE_MAIN_VARIANT (non_reference (TREE_VALUE (parm))) > + != DECL_CONTEXT (fn)) > + /* The single parameter has the wrong type. */ > + return true; > + if (get_constraints (fn)) > + /* Constrained. */ > + return true; > + } > + > + return false; > +} > + > /* Returns true iff class T has a user-provided or explicit constructor. */ > > bool > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc > index 7b296d14a09..0694c28cde3 100644 > --- a/gcc/cp/pt.cc > +++ b/gcc/cp/pt.cc > @@ -23501,9 +23501,13 @@ maybe_adjust_types_for_deduction (tree tparms, > return result; > } > > -/* Return true if computing a conversion from FROM to TO might induce > template > - instantiation. Conversely, if this predicate returns false then computing > - the conversion definitely won't induce template instantiation. */ > +/* Return true if computing a conversion from FROM to TO might consider > + user-defined conversions, which could lead to arbitrary template > + instantiations (e.g. g++.dg/cpp2a/concepts-nondep1.C). If this predicate > + returns false then computing the conversion definitely won't try UDCs. > + > + Note that this restriction parallels LOOKUP_DEFAULTED for CWG1092, but in > + this case we want the early filter to pass instead of fail. */ > > static bool > conversion_may_instantiate_p (tree to, tree from) > @@ -23511,36 +23515,14 @@ conversion_may_instantiate_p (tree to, tree from) > to = non_reference (to); > from = non_reference (from); > > - bool ptr_conv_p = false; > - if (TYPE_PTR_P (to) > - && TYPE_PTR_P (from)) > - { > - to = TREE_TYPE (to); > - from = TREE_TYPE (from); > - ptr_conv_p = true; > - } > - > - /* If one of the types is a not-yet-instantiated class template > - specialization, then computing the conversion might instantiate > - it in order to inspect bases, conversion functions and/or > - converting constructors. */ > - if ((CLASS_TYPE_P (to) > - && !COMPLETE_TYPE_P (to) > - && CLASSTYPE_TEMPLATE_INSTANTIATION (to)) > - || (CLASS_TYPE_P (from) > - && !COMPLETE_TYPE_P (from) > - && CLASSTYPE_TEMPLATE_INSTANTIATION (from))) > - return true; > - > - /* Converting from one pointer type to another, or between > - reference-related types, always yields a standard conversion. */ > - if (ptr_conv_p || reference_related_p (to, from)) > + /* Converting between reference-related types is a standard conversion. */ > + if (reference_related_p (to, from)) > return false; > > /* Converting to a non-aggregate class type will consider its > user-declared constructors, which might induce instantiation. */ > if (CLASS_TYPE_P (to) > - && CLASSTYPE_NON_AGGREGATE (to)) > + && type_has_converting_constructor (to)) > return true; > > /* Similarly, converting from a class type will consider its conversion > @@ -23549,7 +23531,7 @@ conversion_may_instantiate_p (tree to, tree from) > && TYPE_HAS_CONVERSION (from)) > return true; > > - /* Otherwise, computing this conversion definitely won't induce > + /* Otherwise, computing this conversion won't risk arbitrary > template instantiation. */ > return false; > } > diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-nondep5.C > b/gcc/testsuite/g++.dg/cpp2a/concepts-nondep5.C > new file mode 100644 > index 00000000000..f08d4d424da > --- /dev/null > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-nondep5.C > @@ -0,0 +1,34 @@ > +// The from_range_t default ctor should not break the PR99599 workaround > +// { dg-do compile { target c++20 } } > + > +template<typename T> > +struct S { T t; }; > + > +template<typename T> > +concept C = sizeof(S<T>) > 0; > + > +struct I; > + > +struct from_range_t { > + explicit from_range_t() = default; > +}; > +inline constexpr from_range_t from_range; > + > +template<typename T> > +concept FromRange = __is_same_as (T, from_range_t); > + > +//#define WORKAROUND > +#ifdef WORKAROUND > +template<FromRange U, C T> > +void f(U, T*); > +#else > +template<C T> > +void f(from_range_t, T*); > +#endif > + > +void f(...); > + > +void g(I* p) > +{ > + ::f(0, p); > +} > diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-recursive-sat4.C > b/gcc/testsuite/g++.dg/cpp2a/concepts-recursive-sat4.C > index 08f206d3634..41364f7f05a 100644 > --- a/gcc/testsuite/g++.dg/cpp2a/concepts-recursive-sat4.C > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-recursive-sat4.C > @@ -7,7 +7,7 @@ template <class T, class U> concept C = requires(T t, U u) { > t * u; }; > // { dg-error "depends on itself" "" { target *-*-* } .-2 } > > template <class Rep> struct Int { > - Int(); // make the class non-aggregate in light of PR99599 fix > + Int(int = 0); // make the class ineligible for PR99599 workaround > template <class T> requires C<T, Rep> friend void operator*(T, Int) { } > template <class T> requires C<T, Rep> friend void operator*(Int, T) { } > }; > > base-commit: d8dac49707e71844b4d1c21348d92addb19a0969 > -- > 2.49.0 > >