On Tue, Sep 04, 2018 at 05:29:00PM -0400, Jason Merrill wrote: > On Tue, Sep 4, 2018 at 3:02 PM, Marek Polacek <pola...@redhat.com> wrote: > > On Thu, Aug 30, 2018 at 09:22:26AM -0400, Jason Merrill wrote: > >> On Wed, Aug 29, 2018 at 8:03 PM, Marek Polacek <pola...@redhat.com> wrote: > >> > I've now gotten to the point where I question the validity of this PR, > >> > so it's > >> > probably a good time to stop and ask for some advice. > >> > > >> > As discussed in > >> > <https://gcc.gnu.org/ml/gcc-patches/2018-08/msg01607.html>, we > >> > choose the wrong overload for f1: > >> > > >> > struct C { }; > >> > struct A { > >> > operator C() &; > >> > operator C() &&; > >> > }; > >> > > >> > C f1(A a) > >> > { > >> > return a; // should call operator C()&, but calls operator C()&& > >> > } > >> > > >> > Since we're returning a local variable, we know it's about to be > >> > destroyed, > >> > so even though it's got a name, not for very much longer, so we activate > >> > move > >> > semantics. So we perform overload resolution with 'a' turned into > >> > *NON_LVALUE_EXPR<(A&) &a>, an xvalue. We need to convert 'a' from A to > >> > C, > >> > which is taking place in build_user_type_conversion_1. It will see two > >> > cadidates: > >> > > >> > A::operator C() && > >> > A::operator C() & > >> > > >> > when adding these candidates in add_function_candidate we process the > >> > ref-qualifiers by tweaking the type of the implicit object parameter by > >> > turning > >> > it into a reference type. Then we create an implicit conversion sequence > >> > for converting the type of the argument to the type of the parameter, > >> > so A to A&. That succeeds in the first case (an xvalue binding to an > >> > rvalue > >> > reference) but fails in the second case (an xvalue binding to an lvalue > >> > reference). And thus we end up using the first overload. > >> > > >> > But why is this invalid, again? [class.copy.elision] says "or if the > >> > type of > >> > the first parameter of the selected constructor is not an rvalue > >> > reference to > >> > the object's type (possibly cv-qualified), overload resolution is > >> > performed > >> > again, considering the object as an lvalue." but I don't see how that > >> > applies > >> > here. (Constructors can't have ref-qualifiers anyway.) > >> > > >> > Thoughts? > >> > >> Where that rule comes in is when we choose the constructor for C: > >> since we've already called operator C()&&, we choose C(C&&), which > >> does not have a first parameter of "rvalue reference to cv A", so it > >> should be rejected. > > > > I see. Turned out there were two problems: we weren't getting into the > > if (flags & LOOKUP_PREFER_RVALUE) block in build_over_call; this I fixed > > by setting rvaluedness_matches_p in build_user_type_conversion_1 for > > ck_rvalue > > if appropriate. > > > > And then the constructor argument check failed to check the returned > > object's > > type. The tricky part is getting the type. I realized we can go to the > > beginning of the conversion sequence and extract u.expr. But we can't > > simply > > look at its type, if it's DECL_CONV_FN_P, we need to dig deeper. You'll see > > that I don't handle other things, and I don't know if I need to. I tried to > > handle arbitrary expressions, but it evolved into something that just seemed > > too fragile. > > > > E.g. for this testcase u.expr will look like: > > TARGET_EXPR <D.2335, A::operator C ((struct A *) NON_LVALUE_EXPR <(struct A > > &) &a>)> > > and taking its type would yield "struct C". I actually think that returning > > error_mark_node when we see any DECL_CONV_FN_P would work just as well. > > > > Can you think of something better than this? > > Hmm, I think we could push that back even farther, and bail out where > you're already changing build_user_type_conversion_1; if we're doing > this two-step initialization, then we aren't going to end up with a > suitable constructor.
That makes sense. There was one snag: we don't want to make a candidate non-viable only because of LOOKUP_PREFER_RVALUE before the checking if the conversion was ambiguous: if we have two candidates, then killing one of them would make the conversion non-ambiguous (as in template/spec22.C). Bootstrapped/regtested on x86_64-linux, ok for trunk? 2018-09-05 Marek Polacek <pola...@redhat.com> PR c++/87109, wrong overload with ref-qualifiers. * call.c (build_user_type_conversion_1): Use NULL instead of 0. Bail out if performing the maybe-rvalue overload resolution and a conversion function is getting called. * g++.dg/cpp0x/ref-qual19.C: New test. diff --git gcc/cp/call.c gcc/cp/call.c index a1567026975..942b2c204be 100644 --- gcc/cp/call.c +++ gcc/cp/call.c @@ -3971,7 +3971,7 @@ build_user_type_conversion_1 (tree totype, tree expr, int flags, } cand = tourney (candidates, complain); - if (cand == 0) + if (cand == NULL) { if (complain & tf_error) { @@ -4014,6 +4014,12 @@ build_user_type_conversion_1 (tree totype, tree expr, int flags, if (cand->viable == -1) conv->bad_p = true; + /* We're performing the maybe-rvalue overload resolution and + a conversion function is in play. This isn't going to work + because we would not end up with a suitable constructor. */ + if ((flags & LOOKUP_PREFER_RVALUE) && !DECL_CONSTRUCTOR_P (cand->fn)) + return NULL; + /* Remember that this was a list-initialization. */ if (flags & LOOKUP_NO_NARROWING) conv->check_narrowing = true; diff --git gcc/testsuite/g++.dg/cpp0x/ref-qual19.C gcc/testsuite/g++.dg/cpp0x/ref-qual19.C index e69de29bb2d..8494b83e5b0 100644 --- gcc/testsuite/g++.dg/cpp0x/ref-qual19.C +++ gcc/testsuite/g++.dg/cpp0x/ref-qual19.C @@ -0,0 +1,117 @@ +// PR c++/87109 +// { dg-do run { target c++11 } } + +#include <utility> + +struct C { int i; }; +struct D { int i; }; + +struct A { + int j; + operator C() & { return { 1 }; } + operator C() && { return { 2 }; } +}; + +struct B : public A { + operator D() & { return { 3 }; } + operator D() && { return { 4 }; } +}; + +C +f (A a) +{ + return a; +} + +C +f2 (A a) +{ + return std::move (a); +} + +C +f3 () +{ + A a; + return a; +} + +C +f4 () +{ + A a; + return std::move (a); +} + +C +f5 () +{ + return A(); +} + +D +f6 (B b) +{ + return b; +} + +D +f7 (B b) +{ + return std::move (b); +} + +D +f8 () +{ + B b; + return b; +} + +D +f9 () +{ + B b; + return std::move (b); +} + +D +f10 () +{ + return B(); +} + +int +main () +{ + C c1 = f (A()); + if (c1.i != 1) + __builtin_abort (); + C c2 = f2 (A()); + if (c2.i != 2) + __builtin_abort (); + C c3 = f3 (); + if (c3.i != 1) + __builtin_abort (); + C c4 = f4 (); + if (c4.i != 2) + __builtin_abort (); + C c5 = f5 (); + if (c5.i != 2) + __builtin_abort (); + D c6 = f6 (B()); + if (c6.i != 3) + __builtin_abort (); + D c7 = f7 (B()); + if (c7.i != 4) + __builtin_abort (); + D c8 = f8 (); + if (c8.i != 3) + __builtin_abort (); + D c9 = f9 (); + if (c9.i != 4) + __builtin_abort (); + D c10 = f10 (); + if (c10.i != 4) + __builtin_abort (); +}