In the testcase below, we're overeagerly checking the constraints on the conversion function B<int>::operator bool() as part of finding an implicit conversion sequence from B<int> to const A&.
This behavior seems to be nonconforming because according to [over.match.copy] and [over.match.conv], only those conversion functions which yield a type that can be converted to the target type via a standard conversion sequence are candidate functions, and according to [over.match.viable], only candidate functions get their constraints checked. This patch fixes this by swapping the order of the calls to add_candidates and implicit_conversion in build_user_type_conversion_1 when the conversion function has a non-dependent result type. (Conversion function templates with a dependent result type already get handled appropriately in add_template_candidate, it seems.) Bootstrapped and regtested on x86_64-pc-linux-gnu, does this look OK fo trunk? gcc/cp/ChangeLog: * call.c (build_user_type_conversion_1): When the conversion function has a non-dependent result type, pre-check the conversion sequence that would follow the conversion function. gcc/testsuite/ChangeLog: * g++.dg/cpp2a/concepts-conv3.C: New test. --- gcc/cp/call.c | 33 +++++++++++++++++---- gcc/testsuite/g++.dg/cpp2a/concepts-conv3.C | 14 +++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-conv3.C diff --git a/gcc/cp/call.c b/gcc/cp/call.c index bd662518958..6d955ea083f 100644 --- a/gcc/cp/call.c +++ b/gcc/cp/call.c @@ -4099,6 +4099,23 @@ build_user_type_conversion_1 (tree totype, tree expr, int flags, if (TYPE_REF_P (totype)) convflags |= LOOKUP_NO_TEMP_BIND; + conversion *pre_ics = NULL; + if (!uses_template_parms (TREE_TYPE (conv_fns))) + { + /* A conversion function is a candidate function only if it + yields a type that can be converted to the target type. + We check this for conversion functions with non-dependent + result type now to disregard the non-candidate functions + before calling add_candidates. */ + pre_ics = implicit_conversion (totype, + TREE_TYPE (conv_fns), + 0, + /*c_cast_p=*/false, convflags, + complain); + if (!pre_ics) + continue; + } + old_candidates = candidates; add_candidates (TREE_VALUE (conv_fns), first_arg, NULL, totype, NULL_TREE, false, @@ -4112,12 +4129,16 @@ build_user_type_conversion_1 (tree totype, tree expr, int flags, continue; tree rettype = TREE_TYPE (TREE_TYPE (cand->fn)); - conversion *ics - = implicit_conversion (totype, - rettype, - 0, - /*c_cast_p=*/false, convflags, - complain); + conversion *ics; + if (pre_ics) + /* We've already computed the conversion sequence above. */ + ics = pre_ics; + else + ics = implicit_conversion (totype, + rettype, + 0, + /*c_cast_p=*/false, convflags, + complain); /* If LOOKUP_NO_TEMP_BIND isn't set, then this is copy-initialization. In that case, "The result of the 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..bcbc08fe869 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-conv3.C @@ -0,0 +1,14 @@ +// { dg-do compile { target c++20 } } + +template <class T> +struct Error { static constexpr auto value = T::value; }; + +struct A { A(const A&); }; + +template <class T> +struct B { operator bool() requires Error<T>::value; }; + +template <class T> +concept C = requires (B<T> b) { A(b); }; + +static_assert(!C<int>); -- 2.29.0.rc0