From 9324f7a25c7161a813bfae6cc2d180784b165740 Mon Sep 17 00:00:00 2001 From: Jason Merrill Date: Fri, 11 Dec 2020 14:37:09 -0500 Subject: [PATCH] c++: Avoid considering some conversion ops [PR97600] Patrick's earlier patch to check convertibility before constraints for conversion ops wasn't suitable because checking convertibility can also lead to unwanted instantiations, but it occurs to me that there's a smaller check we can do to avoid doing normal consideration of the conversion ops in this case: since we're in the middle of a user-defined conversion, we can exclude from consideration any conversion ops that return a type that would need an additional user-defined conversion to reach the desired type: namely, a type that differs in class-ness from the desired type. [temp.inst]/9 allows optimizations like this: "If the function selected by overload resolution can be determined without instantiating a class template definition, it is unspecified whether that instantiation actually takes place." gcc/cp/ChangeLog: PR libstdc++/97600 * call.c (build_user_type_conversion_1): Avoid considering conversion functions that return a clearly unsuitable type. gcc/testsuite/ChangeLog: * g++.dg/cpp2a/concepts-conv3.C: New test. --- gcc/cp/call.c | 18 +++++++++++++- gcc/testsuite/g++.dg/cpp2a/concepts-conv3.C | 25 ++++++++++++++++++++ gcc/testsuite/g++.dg/cpp2a/concepts-conv3a.C | 17 +++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-conv3.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-conv3a.C diff --git a/gcc/cp/call.c b/gcc/cp/call.c index 221e3de0c70..c2d62e582bf 100644 --- a/gcc/cp/call.c +++ b/gcc/cp/call.c @@ -4025,9 +4025,9 @@ build_user_type_conversion_1 (tree totype, tree expr, int flags, creating a garbage BASELINK; constructors can't be inherited. */ ctors = get_class_binding (totype, complete_ctor_identifier); + tree to_nonref = non_reference (totype); if (MAYBE_CLASS_TYPE_P (fromtype)) { - tree to_nonref = non_reference (totype); if (same_type_ignoring_top_level_qualifiers_p (to_nonref, fromtype) || (CLASS_TYPE_P (to_nonref) && CLASS_TYPE_P (fromtype) && DERIVED_FROM_P (to_nonref, fromtype))) @@ -4111,6 +4111,22 @@ build_user_type_conversion_1 (tree totype, tree expr, int flags, tree conversion_path = TREE_PURPOSE (conv_fns); struct z_candidate *old_candidates; + /* If LOOKUP_NO_CONVERSION, don't consider a conversion function that + would need an addional user-defined conversion, i.e. if the return + type differs in class-ness from the desired type. So we avoid + considering operator bool when calling a copy constructor. + + This optimization avoids the failure in PR97600, and is allowed by + [temp.inst]/9: "If the function selected by overload resolution can be + determined without instantiating a class template definition, it is + unspecified whether that instantiation actually takes place." */ + tree convtype = non_reference (TREE_TYPE (conv_fns)); + if ((flags & LOOKUP_NO_CONVERSION) + && !WILDCARD_TYPE_P (convtype) + && (CLASS_TYPE_P (to_nonref) + != CLASS_TYPE_P (convtype))) + continue; + /* If we are called to convert to a reference type, we are trying to find a direct binding, so don't even consider temporaries. If we don't find a direct binding, the caller will try again to diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-conv3.C b/gcc/testsuite/g++.dg/cpp2a/concepts-conv3.C new file mode 100644 index 00000000000..d53f37c10e6 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-conv3.C @@ -0,0 +1,25 @@ +// { dg-do compile { target c++20 } } + +// Here, normal overload resolution would consider B::operator bool when +// evaluating A(b), leading to a hard error instantiating Error, but we +// avoid considering it by noticing that converting bool (a scalar) to A (a +// class) would require a user-defined conversion, which is not allowed when +// we're already dealing with the user-defined conversion to A. + +// This seems to be allowed by [temp.inst]/9: "If the function selected by +// overload resolution (12.4) can be determined without instantiating a class +// template definition, it is unspecified whether that instantiation actually +// takes place." + +template +struct Error { static constexpr auto value = T::value; }; + +struct A { A(const A&); }; + +template +struct B { operator bool() requires Error::value; }; + +template +concept C = requires (B b) { A(b); }; + +static_assert(!C); diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-conv3a.C b/gcc/testsuite/g++.dg/cpp2a/concepts-conv3a.C new file mode 100644 index 00000000000..7e9e28474d9 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-conv3a.C @@ -0,0 +1,17 @@ +// { dg-do compile { target c++20 } } + +// But make sure we do consider template conversions that could produce the +// right type. + +template +struct Error { static constexpr auto value = T::value; }; // { dg-error "not a member" } + +struct A { A(const A&); }; + +template +struct B { template operator U() requires Error::value; }; + +template +concept C = requires (B b) { A(b); }; // { dg-message "required from here" } + +static_assert(!C);