[cfe-dev] Strange phenomenon with conversion operator and call of overloaded method

Jonathan Sauer jonathan.sauer at gmx.de
Wed Jun 1 10:01:43 PDT 2011


Hello,

thank you for your (as always) detailed answer!

>> struct Foo { };     // (A)
>> //using Foo = int;    // (B)
>> 
>> template <typename T>
>> class A {
>>    public:
>>        operator const T&() const { return m_t; }
>> 
>>    private:
>>        T   m_t;
>> };
>> 
>> static void bar(Foo&&) { }  // (C)
>> static void bar(const Foo&) { }
>> 
>> int main(int, char**)
>> {
>>    A<Foo>              a;
>>    bar(a);
>>    bar((const Foo&) a);  // (D)
>> }
> 
> You can interpret the spec so that it yields exactly clang's behavior, I 
> think. The spec says that for a reference binding of "Foo&&" to "A<Foo>", 
> the following conversion functions are candidates (S is the initializer, 
> A<Foo>, and T is the type "Foo"):
> 
> "Those that are not hidden within S and yield type “lvalue reference to cv2 
> T2” (when 8.5.3 requires an lvalue result) or “cv2 T2” or “rvalue reference 
> to cv2 T2” (when 8.5.3 requires an rvalue result), where “cv1 T” is 
> reference-compatible (8.5.3) with “cv2 T2”, are candidate functions."
> 
> This reads to me that "operator const T&" is *not* a candidate here, because 
> "const T&" is an lvalue reference. However, the example in 8.5.3 indicates 
> that the intent seems that we *do* consider "operator const T&" as a 
> candidate. Consider the similar example it gives at 8.5.3:
> 
> struct X {
> operator B();
> operator int&();
> } x;
> 
> int &&rri2 = X(); // ill-formed: lvalue-to-rvalue conversion on result of 
> operator int&

I agree with this reading. In 8.5.3p5, a bit above the example you posted, is
an example that is not ill-formed:

struct A { };
struct B : A { operator int&(); } b;

int& ir = B(); // ir refers to the result of B::operator int&

> So assuming that clang follows this intent, we would have it choose 
> "operator const T&" as a candidate conversion function, and (because we are 
> initializing an rvalue reference), will do an lvalue to rvalue conversion on 
> the lvalue it yields (according to 3.10p2).

I don't quite understand. Why would we need to do a conversion from lvalue to
rvalue here? Just because the first bar method takes an rvalue reference?

> So for the first "bar", you bind an rvalue reference to an rvalue. For the 
> second bar, the conversion sequence would bind an lvalue reference to an 
> lvalue. These two user defined conversion sequences use the same conversion 
> function, and hance can be compared as follows:
> 
> - The first bar's parameter conversion sequence wins, because the second 
> conversion function binds an rvalue reference to an rvalue, while the 
> competing conversion sequence binds an lvalue reference to an lvalue (c.f. 
> second toplevel bullet of 13.3.3.2p3 and one of the subbullets of the former 
> toplevel bullets).
> 
> Clang thus selects the first bar, and then the initialization of the 
> parameter fails, because of 8.5.3 saying that when an lvalue to rvalue 
> conversion happens, the program is ill-formed. 

The thing that still confuses me is that the error only occurs when a conversion operator
is used, not a vanilla get-method:

struct Foo { };     // (A)
//using Foo = void*;    // (B)

template <typename T>
class A {
    public:
        operator const T&() const { return m_t; }
        const T& get() const { return m_t; }
        
    private:
        T   m_t;
};

static void bar(Foo&&) { }
static void bar(const Foo&) { }

int main(int, char**)
{
    A<Foo>              a;
    bar(a.get());
}


This code compiles, even though the overload resolution is the same as when using
the conversion operator.

And the code also compiles when using line (B) instead of (A) (slightly changed from
my initial example to avoid integer conversions).

This is what I don't understand right now.


Jonathan





More information about the cfe-dev mailing list