Strange phenomenon with conversion operator and call of overloaded method

Hello,

while fooling around with conversion operators, I stumbled upon the following:

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)
}

Compiling this program with clang r132331 in C++0x mode results in the following error message:

clang.cpp:19:9: error: no viable conversion from 'A<Foo>' to 'Foo'
    bar(a);
        ^
clang.cpp:1:8: note: candidate constructor (the implicit copy constructor) not viable: no known
      conversion from 'A<Foo>' to 'const Foo &' for 1st argument
struct Foo { }; // (A)
       ^
clang.cpp:7:9: note: candidate function
        operator const T&() const { return m_t; }
        ^
clang.cpp:13:22: note: passing argument to parameter here
static void bar(Foo&&) { } // (C)

First of all, clang does not tell why the noted candidate function is not viable. Second of all,
for some reason clang does not consider the overload bar(const Foo&), even though it would match.

When replacing line (A) with line (B) (making Foo an int instead of a class), the code compiles.
This is really strange.

When commenting out line (C) (making bar non-overloaded), the code compiles as well.

Helping the compiler as in line (D) also results in a successful compile.

Why does this happen?

With many thanks in advance,
Jonathan

Jonathan Sauer wrote:

Hello,

while fooling around with conversion operators, I stumbled upon the
following:

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)
}

Compiling this program with clang r132331 in C++0x mode results in the
following error message:

clang.cpp:19:9: error: no viable conversion from 'A<Foo>' to 'Foo'
    bar(a);
        ^
clang.cpp:1:8: note: candidate constructor (the implicit copy constructor)
not viable: no known
      conversion from 'A<Foo>' to 'const Foo &' for 1st argument
struct Foo { }; // (A)
       ^
clang.cpp:7:9: note: candidate function
        operator const T&() const { return m_t; }
        ^
clang.cpp:13:22: note: passing argument to parameter here
static void bar(Foo&&) { } // (C)

First of all, clang does not tell why the noted candidate function is not
viable. Second of all, for some reason clang does not consider the
overload bar(const Foo&), even though it would match.

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&

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).

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.

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

I just tried the code using GCC 4.5.1 on Ideone (<http://ideone.com/9UbQv>),
and it compiled fine -- but for some reason GCC chose bar(Foo&&) instead of
bar(const Foo&) ...

Strange.

Jonathan

Jonathan Sauer wrote:

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&

I'm now more confident than before that the description at 8.5.3 about an
lvalue to rvalue conversion happening as the second standard conversion
sequence in the user defined conversion sequence is not correct. The
description of the user defined conversion sequence at 13.3.3.1.4p1 says
(note that this is a "direct binding" according to 8.5.3):

"If the parameter binds directly to the result of applying a conversion
function to the argument expression, the implicit conversion sequence is a
user-defined conversion sequence (13.3.3.1.2), with the second standard
conversion sequence either an identity conversion or, if the conversion
function returns an entity of a type that is a derived class of the
parameter type, a derived-to-base Conversion"

Hence, there simply cannot be an lvalue to rvalue conversion in that user
defined conversion sequence. By that text, the user defined conversion
sequence is an identity conversion. So, by 13.3.3.1.4p1, this would be a
case where a standard conversion sequence (the second one of our UCS) would
require binding an rvalue reference to an lvalue (because by 13.3.3.1.4,
there is no lvalue to rvalue conversion, at least not for the purpose of
constructing an implicit conversion sequence). Such a conversion sequence
cannot exist by 13.3.3.1.4p3, and hence the whole user defined conversion
sequence cannot exist, and the description and example in 8.5.3p5 seems to
be defective (I suspected this earlier from the sight of 13.3.1.6, but now
we have another paragraph that seem to contradict 8.5.3p5).

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?

I have no idea anymore. I prefer to not base my argument on 8.5.3 anymore,
unless someone that has more insight into the intention behind 8.5.3 speaks
up and clarifies that for us. I may be missing something really important
here.