operator+ overload resolution

R

Richard Hayden

Hi,

I have the following code for example:

/**********************************/

#include <iostream>

class C1 {
public:
int a;

C1(int b) : a(b) { }

int operator+(C1& o) {
std::cout << "C1 Op+ CALLED";
return a + o.a;
}
};

class C2 : public C1 {
public:
C2(int b) : C1(b) { }

operator int() const { return a; }

int operator+(int o) {
std::cout << "C2 Op+ CALLED";
return o + a;
}
};

int main(int argc, char** argv) {
C2 o1(2);
C2 o2(3);

C2 o3 = o1 + o2;

return 0;
}

/**********************************/

Surprisingly this compiles (with g++) and outputs 'C2 Op+ CALLED'.
However, surely this should not compile since the call to operator+ is
surely ambiguous? int operator+(int o) called with an O2 requires one
cast (C2 -> int, which is defined explicitly in the class) and so does
int operator+(C1& o) (C2& -> C1&). What's going on here? Have I
misunderstood function overload resolution? Is g++ implementing the
standard incorrectly?

Thanks,

Richard Hayden.
 
R

Rob Williscroft

Richard Hayden wrote in in
comp.lang.c++:
class C1 {
int operator+(C1& o) {
};

class C2 : public C1 {
int operator+(int o) {
};

int main(int argc, char** argv) {
C2 o1(2);
C2 o2(3);

C2 o3 = o1 + o2;

return 0;
}

/**********************************/

Surprisingly this compiles (with g++) and outputs 'C2 Op+ CALLED'.
However, surely this should not compile since the call to operator+ is
surely ambiguous? int operator+(int o) called with an O2 requires one
cast (C2 -> int, which is defined explicitly in the class) and so does
int operator+(C1& o) (C2& -> C1&). What's going on here? Have I
misunderstood function overload resolution? Is g++ implementing the
standard incorrectly?

Your analysis is missing a conversion from C2 to C1, you're analysing
the right hand side only and ignoring the left hand side:

operator +( C2, C2 ) is

A) C2::eek:perator +( C2, C2 |-> int ) or

B) C1::eek:perator +( C2 |-> C1, C2 |-> C1 )

(A) requires a shorter conversion sequence in the first paramiter
to operator + and is choosen.

Rob.
 
A

Andrey Tarasevich

Richard said:
...
I have the following code for example:

/**********************************/

#include <iostream>

class C1 {
public:
int a;

C1(int b) : a(b) { }

int operator+(C1& o) {
std::cout << "C1 Op+ CALLED";
return a + o.a;
}
};

class C2 : public C1 {
public:
C2(int b) : C1(b) { }

operator int() const { return a; }

int operator+(int o) {
std::cout << "C2 Op+ CALLED";
return o + a;
}
};

int main(int argc, char** argv) {
C2 o1(2);
C2 o2(3);

C2 o3 = o1 + o2;

return 0;
}

/**********************************/

Surprisingly this compiles (with g++) and outputs 'C2 Op+ CALLED'.
However, surely this should not compile since the call to operator+ is
surely ambiguous? int operator+(int o) called with an O2 requires one
cast (C2 -> int, which is defined explicitly in the class) and so does
int operator+(C1& o) (C2& -> C1&). What's going on here? Have I
misunderstood function overload resolution? Is g++ implementing the
standard incorrectly?
...

According to 13.3.1.2/3, the set of member candidates in this case is
created by qualified lookup for 'C2::eek:perator+'. But this lookup will
not find the inherited 'C1::eek:perator+' in 'C2' simply because it is
hidden in 'C2'. I.e. 'C1::eek:perator+' is not even considered as a viable
function. For this reason there's no ambiguity.

The potential for ambiguity will appear if you add a 'using' declaration
of 'C1::eek:perator+' to 'C2's definition

using C1::eek:perator+;

In this case 'C1::eek:perator+' will be found by name lookup and added to
the set of viable functions. Calling this function will require two
implicit conversions of 'reference binding' type (see 13.3.3.1.4): one
for the implied object argument and another for the explicit argument of
'C1::eek:perator+'. Both bind a 'C1&' to an object of 'C2' type. To me it
looks like it should result in an ambiguity. Such bindings are given the
'Conversion' rank, which means that 'C1::eek:perator+' requires worse
conversion sequence for the implied object argument than 'C2::eek:perator+'
(which requires no conversion). At the same time 'C2::eek:perator+'
requires worse conversion sequence (user-defined) for the explicit
argument than 'C1::eek:perator+'.

However, Comeau accepts the modified code and chooses 'C1::eek:perator+'
(!). Maybe I'm missing something...
 
R

Rob Williscroft

Rob Williscroft wrote in 130.133.1.4 in comp.lang.c++:
Your analysis is missing a conversion from C2 to C1, you're analysing
the right hand side only and ignoring the left hand side:

operator +( C2, C2 ) is

A) C2::eek:perator +( C2, C2 |-> int ) or

B) C1::eek:perator +( C2 |-> C1, C2 |-> C1 )

(A) requires a shorter conversion sequence in the first paramiter
to operator + and is choosen.

And I missed that C2::eek:perator + hiddes C1::eek:perator +,
fortunatly Andrey Tarasevich didn't miss it.

Rob.
 
M

Mark

On Thu, 08 Jul 2004 18:39:42 -0700, Andrey Tarasevich

[...]
According to 13.3.1.2/3, the set of member candidates in this case is
created by qualified lookup for 'C2::eek:perator+'. But this lookup will
not find the inherited 'C1::eek:perator+' in 'C2' simply because it is
hidden in 'C2'. I.e. 'C1::eek:perator+' is not even considered as a viable
function. For this reason there's no ambiguity.

The potential for ambiguity will appear if you add a 'using' declaration
of 'C1::eek:perator+' to 'C2's definition

using C1::eek:perator+;

In this case 'C1::eek:perator+' will be found by name lookup and added to
the set of viable functions. Calling this function will require two
implicit conversions of 'reference binding' type (see 13.3.3.1.4): one
for the implied object argument and another for the explicit argument of
'C1::eek:perator+'. Both bind a 'C1&' to an object of 'C2' type. To me it
looks like it should result in an ambiguity. Such bindings are given the
'Conversion' rank, which means that 'C1::eek:perator+' requires worse
conversion sequence for the implied object argument than 'C2::eek:perator+'
(which requires no conversion). At the same time 'C2::eek:perator+'
requires worse conversion sequence (user-defined) for the explicit
argument than 'C1::eek:perator+'.
My standard is not currently is not within arm's reach, nonetheless
I'm not sure if I'm following the meaning behind 'worse conversion
sequence' for the implied and the explict object.

For C2 worse than C1 (the explicit argument), the fact that the OP
only defined operator+ with an int parameter, required a casts of o2
to an int to perform the addition. Yes/No? If yes, how's C2 worse
than C1?

For C1 worse than C2, perhaps I'm misunderstanding the meaning of
'implied object argument'. Elaborate if you will.

Thanks

Mark
 
A

Andrey Tarasevich

Mark said:
My standard is not currently is not within arm's reach, nonetheless
I'm not sure if I'm following the meaning behind 'worse conversion
sequence' for the implied and the explict object.

For C2 worse than C1 (the explicit argument), the fact that the OP
only defined operator+ with an int parameter, required a casts of o2
to an int to perform the addition. Yes/No? If yes, how's C2 worse
than C1?

'C2::eek:perator+' is worse than 'C1::eek:perator+' because in case of
'C2::eek:perator+' a conversion from explicit argument type 'C2' to
explicit parameter type 'int' is required. This can only be done by a
user-defined conversion in this case.

In case of 'C1::eek:perator+' another conversion is required: from explicit
argument type 'C2' to explicit parameter type 'C1&'. As far as I
understand the standard, this conversion is treated as a standard one
(even though it is not included in the list of "official" standard
conversions). For this reason 'C1::eek:perator+' is "better" than
'C2::eek:perator+' (standard conversions are always "better" than
user-defined ones).
For C1 worse than C2, perhaps I'm misunderstanding the meaning of
'implied object argument'. Elaborate if you will.

Candidate member functions of class 'T' are considered to have an extra
parameter referred to as 'implied object parameter'. This parameter has
type 'T&' (or 'const/volatile T&' for const/volatile member functions).
This parameter is bound to the object, for which the member function has
been invoked. (That's what I referred to as 'implied object argument'.
Not a good choice of words. The argument is not really 'implied'. I
should've called it simply 'object argument'). Conversions that are
required in order to bind this parameter are also taken into account by
overload resolution. Once again, if I understood the standard correctly,
in this case the implied object parameter for 'C1::eek:perator+' has type
'C1&' and the object has type 'C2', i.e. a conversion from 'C2' to 'C1&'
is required (ranked as 'Conversion'). The implied object parameter for
'C2::eek:perator+' has type 'C2&' and and the object has type 'C2', i.e. no
conversion is required (ranked as 'Exact match'). That's why
'C2::eek:perator+' is "better" from this point of view ('Exact
match'-ranked conversions are always "better" than 'Conversion'-ranked
ones).

It possible that the above explanation contains some mistakes caused by
my misunderstanding of some portions of the standard. Comeau's behavior
also suggests that. It would be great to get to the bottom of this issue.
 
R

Rob Williscroft

Andrey Tarasevich wrote in in
comp.lang.c++:
According to 13.3.1.2/3, the set of member candidates in this case is
created by qualified lookup for 'C2::eek:perator+'. But this lookup will
not find the inherited 'C1::eek:perator+' in 'C2' simply because it is
hidden in 'C2'. I.e. 'C1::eek:perator+' is not even considered as a
viable function. For this reason there's no ambiguity.

The potential for ambiguity will appear if you add a 'using'
declaration of 'C1::eek:perator+' to 'C2's definition

using C1::eek:perator+;

In this case 'C1::eek:perator+' will be found by name lookup and added to
the set of viable functions. Calling this function will require two
implicit conversions of 'reference binding' type (see 13.3.3.1.4): one
for the implied object argument and another for the explicit argument
of 'C1::eek:perator+'. Both bind a 'C1&' to an object of 'C2' type. To me
it looks like it should result in an ambiguity. Such bindings are
given the 'Conversion' rank, which means that 'C1::eek:perator+' requires
worse conversion sequence for the implied object argument than
'C2::eek:perator+' (which requires no conversion). At the same time
'C2::eek:perator+' requires worse conversion sequence (user-defined) for
the explicit argument than 'C1::eek:perator+'.

However, Comeau accepts the modified code and chooses 'C1::eek:perator+'
(!). Maybe I'm missing something...

This is a good question, last night I just didn't know the answer, this
morning, I thought "but isn't a (member) using declaration a bit like":

class B
{
public:
void f();
};

class D : B
{
public:
// using B::f;
void f () { B::f(); }
}

It took me about 1/2 an hour to find it in the standard:

7.3.3/13: (Theusingdeclaration)

For the purpose of overload resolution, the functions which are
introduced by a using-declaration into a derived class will be treated
as though they were members of the derived class. In particular, the
implicit this parameter shall be treated as if it were a pointer to the
derived class rather than to the base class. This has no effect on the
type of the function, and in all other respects the function remains a
member of the base class.

So my "... a bit like" is correct (only) as far as overload
resolution goes.

Rob. -- http://www.victim-prime.dsl.pipex.com/
 
A

Andrey Tarasevich

Rob said:
...
7.3.3/13: (Theusingdeclaration)

For the purpose of overload resolution, the functions which are
introduced by a using-declaration into a derived class will be treated
as though they were members of the derived class. In particular, the
implicit this parameter shall be treated as if it were a pointer to the
derived class rather than to the base class. This has no effect on the
type of the function, and in all other respects the function remains a
member of the base class.
...

Yes, that explains the Comeau's behavior. The first sentence at least. I
find everything after the first one a bit misleading since it talks
about implict parameter as a _pointer_, while in overload resolution
sections the standard defines the implied object parameter to be a
_reference_.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top