On Tue, 27 Oct 2020, Jason Merrill wrote:

> On 10/26/20 5:37 PM, Patrick Palka wrote:
> > 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.
> 
> Hmm.  I agree with your reading of the standard, but I'm not sure this is what
> we actually want.  It's the reverse of the agreed direction for CWG2369, to
> check constraints before non-dependent conversions for function arguments, and
> goes against the more general principle that the fix for unwanted
> instantiation should be additional constraints.
> 
> A different testcase would succeed before your patch, and fail after:
> 
> template <class T>
> struct Error { static constexpr auto value = T::value; };
> 
> struct A { A(const A&); };
> 
> template <class T>
> struct B { operator Error<T>*() requires false; };
> 
> template <class T>
> concept C = requires (B<T> b) { (A*)b; };
> 
> static_assert(!C<int>);
> 
> I've raised this on the reflector.

I see, thanks.  I should note I originally hit this issue in some of the
<ranges> testcases while working on
https://gcc.gnu.org/pipermail/gcc-patches/2020-October/557237.html
to make us cache satisfaction results aggressively.

Though after some experimentation, I managed to come up with a <ranges>
testcase that misbehaves in the same way, even with GCC 10's
conservative satisfaction caching.  I filed PR libstdc++/96700 and
posted a more detailed analysis of the problem.

> 
> > 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.)
> 
> Testcase?
> 
> But yes, for dependent result types if there isn't a conversion, deduction
> will fail.

Ah, indeed.  Here's an updated patch that adds to the testcase a
conversion function template for which deduction fails, to verify we
don't check its constraints either:

-- >8 --

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                               | 31 +++++++++++++++++----
 gcc/testsuite/g++.dg/cpp2a/concepts-conv3.C | 19 +++++++++++++
 2 files changed, 44 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..01adc5dc2ab 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -4099,6 +4099,22 @@ 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.
+            For conversion functions with non-dependent result type,
+            we check this now to discard non-candidates before calling
+            add_candidates.  */
+         pre_ics = implicit_conversion (totype, TREE_TYPE (conv_fns),
+                                        /*expr=*/NULL_TREE,
+                                        /*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 +4128,15 @@ 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)
+           /* No need to recompute it.  */
+           ics = pre_ics;
+         else
+           ics = implicit_conversion (totype, rettype,
+                                     /*expr=*/NULL_TREE,
+                                      /*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..e0e49c737a8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-conv3.C
@@ -0,0 +1,19 @@
+// { 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 U>
+  operator U*() requires Error<T>::value;
+};
+
+template <class T>
+concept C = requires (B<T> b) { A(b); };
+
+static_assert(!C<int>);
-- 
2.29.0.rc0

Reply via email to