-->

Non-const copy constructor and implicit conversion

2019-02-07 02:10发布

问题:

Consider the following C++ code:

struct B { };
struct A
{
        A(int);
        A(A&); // missing const is intentional
        A(B);
        operator B();
};

A f()
{
        // return A(1); // compiles fine
        return 1; // doesn't compile
}

This compiles fine on MSVC++ 2010 (in fact, on MSVC it even works if I remove B altogether). It doesn't on GCC 4.6.0:

conv.cpp: In function ‘A f()’:
conv.cpp:13:9: error: no matching function for call to ‘A::A(A)’
conv.cpp:13:9: note: candidates are:
conv.cpp:6:2: note: A::A(B)
conv.cpp:6:2: note:   no known conversion for argument 1 from ‘A’ to ‘B’
conv.cpp:5:2: note: A::A(A&)
conv.cpp:5:2: note:   no known conversion for argument 1 from ‘A’ to ‘A&’
conv.cpp:4:2: note: A::A(int)
conv.cpp:4:2: note:   no known conversion for argument 1 from ‘A’ to ‘int’

What's confusing me is the message no known conversion for argument 1 from ‘A’ to ‘B’. How can this be true considering that A::operator B() is very well defined?

回答1:

The error is quite clear on the list of candidates that were rejected. The problem is that implicit conversion sequences involving a user defined conversion in the C++ language are limited to a single user defined conversion:

§13.3.3.1.2 [over.ics.user]/1 A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user-defined conversion (12.3) followed by a second standard conversion sequence.

The standard conversion sequences are defined in §4[conv]:

[...] A standard conversion sequence is a sequence of standard conversions in the following order

  • Zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion, and function-to-pointer conversion.

  • Zero or one conversion from the following set: integral promotions, floating point promotion, integral conversions, floating point conversions, floating-integral conversions, pointer conversions, pointer to member conversions, and boolean conversions.

  • Zero or one qualification conversion.

The problem is that your code cannot get from point a) int rvalue to point b) B by applying a single user defined conversion.

In particular, all conversion sequences that are available start with a user defined conversion (implicit constructor A(int)) that yield an A rvalue. From there, the rvalue cannot be bound to a non-const reference to call A::A( A& ), so that path is discarded. All the other paths require a second user defined conversion that is not allowed, and in fact the only other path that would get us to point b) requires two other user defined conversions for a total of 3.



回答2:

Because you cannot do more than one implicit conversion. You would have to go A::A(A::A(int)::operator B()) to make that work, and that's way too many steps for the compiler to figure out on it's own.



回答3:

I don't think that "too many steps to figure on its own" as DeadMG pointed out is the reason. I've had constructs with 3-4 conversions, and the compiler always figured them out just fine.

I believe the problem is rather that the compiler is not allowed to convert a const reference to a non-constreference on its own behalf (it is only allowed to do that when you explicitly tell it with a cast).
And since the reference to the temporary object that is passed to the copy constructor is const, but the copy constructor is not, it doesn't find a suitable function.

EDIT: I didn't find any "real" code (see comments below) but constructed a multi-zigzag-convert example that actually compiles without errors under gcc 4.5. Note that this compiles just fine with -Wall -Wextra too, which frankly surprises me.

struct B
{
    signed int v;
    B(unsigned short in) : v(in){}
};

struct C
{
    char v;
    C(int in) : v(in){}
};

struct A
{
    int v;
    A(B const& in) : v(in.v){}
    operator C() { return C(*this); }
};

enum X{ x = 1 };

int main()
{
    C c = A(x);
    return 0;
}


回答4:

The error lists all the potential candidates to be used, and why they cannot be used. It lists the conversion from B because its one of the constructors, but it doesn't know how to use it in this case, so it doesn't.