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