why no ambiguity error generated

  • Thread starter subramanian100in
  • Start date
S

subramanian100in

consider the following program:

#include <iostream>

using namespace std;

class my_complex
{
public:
friend ostream & operator<<(ostream &os, const my_complex &c);
my_complex(double r, double i = 10.0) : re(r), im(i) { }
my_complex(const my_complex &rc) : re(rc.re), im(rc.im) { }
my_complex operator+(const my_complex &r);
friend my_complex operator+(const my_complex &c1, const
my_complex &c2);

private:
double re;
double im;
};

ostream & operator<<(ostream &os, const my_complex &c)
{
os << "re = " << c.re << " im = " << c.im << endl;
return os;
}

my_complex my_complex::eek:perator+(const my_complex &r)
{
cout << "from member operator+" << endl;
return my_complex(re + r.re, im + r.im);
}

my_complex operator+(const my_complex &c1, const my_complex &c2)
{
cout << "from friend operator+" << endl;
my_complex c(c1.re + c2.re, c1.im + c2.im);
return c;
}

int main()
{
my_complex obj(1, 2);

cout << 5.0 + obj;

cout << obj + 5.0;

return 0;
}

The output of the above program under both VC++2005 Express Edition
and g++, is

from friend operator+
re = 6 im = 12
from member operator+
re = 6 im = 12

I thought ambiguity error would be generated for the expressions '5.0
+ obj' and 'obj + 5.0' because I have defined operator+() both as
member function and as friend function. But my understanding seems to
be wrong. Kindly clarify where I am going wrong.

Thanks
V.Subramanian
 
A

Abhishek Padmanabh

consider the following program:

#include <iostream>

using namespace std;

class my_complex
{
public:
friend ostream & operator<<(ostream &os, const my_complex &c);
my_complex(double r, double i = 10.0) : re(r), im(i) { }
my_complex(const my_complex &rc) : re(rc.re), im(rc.im) { }
my_complex operator+(const my_complex &r);
friend my_complex operator+(const my_complex &c1, const
my_complex &c2);

private:
double re;
double im;

};

ostream & operator<<(ostream &os, const my_complex &c)
{
os << "re = " << c.re << " im = " << c.im << endl;
return os;

}

my_complex my_complex::eek:perator+(const my_complex &r)
{
cout << "from member operator+" << endl;
return my_complex(re + r.re, im + r.im);

}

my_complex operator+(const my_complex &c1, const my_complex &c2)
{
cout << "from friend operator+" << endl;
my_complex c(c1.re + c2.re, c1.im + c2.im);
return c;

}

int main()
{
my_complex obj(1, 2);

cout << 5.0 + obj;

cout << obj + 5.0;

return 0;

}

The output of the above program under both VC++2005 Express Edition
and g++, is

from friend operator+
re = 6 im = 12
from member operator+
re = 6 im = 12

I thought ambiguity error would be generated for the expressions '5.0
+ obj' and 'obj + 5.0' because I have defined operator+() both as
member function and as friend function. But my understanding seems to
be wrong. Kindly clarify where I am going wrong.

It is not an ambiguity because of const-correctness. The member is not
a const member function. Because of that the first implicit 'this'
argument is non-const. While for the friend function, both arguments
are const. So, there's your difference. Make the member const and you
will find the ambiguity.
 
S

subramanian100in

On Dec 3, 7:21 pm, "(e-mail address removed), India"

Now I have made both the object as well as the operator+() member
function as const. ie consider the modified program:

#include <iostream>

using namespace std;

class my_complex
{
public:
friend ostream & operator<<(ostream &os, const my_complex &c);
my_complex(double r, double i = 10.0) : re(r), im(i) { }
my_complex(const my_complex &rc) : re(rc.re), im(rc.im) { }
my_complex operator+(const my_complex &r) const;
friend my_complex operator+(const my_complex &c1, const my_complex
&c2);

private:
double re;
double im;
};

ostream & operator<<(ostream &os, const my_complex &c)
{
os << "re = " << c.re << " im = " << c.im << endl;
return os;
}

my_complex my_complex::eek:perator+(const my_complex &r) const
{
cout << "from member operator+" << endl;
return my_complex(re + r.re, im + r.im);
}

my_complex operator+(const my_complex &c1, const my_complex &c2)
{
cout << "from friend operator+" << endl;
my_complex c(c1.re + c2.re, c1.im + c2.im);
return c;
}

int main()
{
const my_complex obj(1, 2);

cout << 5.0 + obj;

// cout << obj + 5.0;

return 0;
}

While I am getting ambiguity error now for the commented statement,
there is still NO AMBIGUITY error generated for
'5.0 + obj'. I just now found in page 264 in the book "C++ FAQs" 2nd
Edition by Marshall Cline that C++ never promotes the 'this' object in
a member function invocation. If this is the reason(for no ambiguity),
then please explain why this rule is so - ie why a member function is
not called after promoting the 'this' object ?

If there is any other reason, kindly explain.

Thanks
V.Subramanian
 
A

Abhishek Padmanabh

* On Dec 3, 7:37 pm, Abhishek Padmanabh <[email protected]>

<snipped code>

While I am getting ambiguity error now for the commented statement,
there is still NO AMBIGUITY error generated for
'5.0 + obj'. I just now found in page 264 in the book "C++ FAQs" 2nd
Edition by Marshall Cline that C++ never promotes the 'this' object in
a member function invocation. If this is the reason(for no ambiguity),
then please explain why this rule is so - ie why a member function is
not called after promoting the 'this' object ?

If there is any other reason, kindly explain.

I am not sure on this. That could be the reason as I could not find
anything specific regarding this in the standards (don't blame it,
blame me. I just could not find it, it must be there somewhere).
However, I am failing to see where the 'this' object promotion is
required.
 
V

Victor Bazarov

Now I have made both the object as well as the operator+() member
function as const. ie consider the modified program:

#include <iostream>

using namespace std;

class my_complex
{
public:
friend ostream & operator<<(ostream &os, const my_complex &c);
my_complex(double r, double i = 10.0) : re(r), im(i) { }
my_complex(const my_complex &rc) : re(rc.re), im(rc.im) { }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is really unnecessary.
my_complex operator+(const my_complex &r) const;
friend my_complex operator+(const my_complex &c1, const my_complex
&c2);

private:
double re;
double im;
};
[..]

int main()
{
const my_complex obj(1, 2);

cout << 5.0 + obj;

// cout << obj + 5.0;

return 0;
}

While I am getting ambiguity error now for the commented statement,
there is still NO AMBIGUITY error generated for
'5.0 + obj'. I just now found in page 264 in the book "C++ FAQs" 2nd
Edition by Marshall Cline that C++ never promotes the 'this' object in
a member function invocation.

I believe you're misinterpreting something in the book. C++ never
seeks out conversions for the left operand for any binary operator.
That's the reason why '5.0 + obj' is never resolved to the member
function because double (the type of '5.0') does not have members.
If this is the reason(for no ambiguity),
then please explain why this rule is so - ie why a member function is
not called after promoting the 'this' object ?

There is no "promoting the 'this' object". The compiler finds two
(or more) operator+() functions in the global namespace. Only then
it tries to find the correct types to which to convert the operands
(arguments). Since 'my_complex' cannot be converted to 'double',
::eek:perator+(double,double) cannot be used. But '5.0' can be converted
to 'my_complex', so ::eek:perator+(my_complex const&, my_complex const&)
is a viable function.

V
 
J

James Kanze

consider the following program:
#include <iostream>
using namespace std;
class my_complex
{
public:
friend ostream & operator<<(ostream &os, const my_complex &c);
my_complex(double r, double i = 10.0) : re(r), im(i) { }
my_complex(const my_complex &rc) : re(rc.re), im(rc.im) { }
my_complex operator+(const my_complex &r);
friend my_complex operator+(const my_complex &c1, const
my_complex &c2);
private:
double re;
double im;
};
ostream & operator<<(ostream &os, const my_complex &c)
{
os << "re = " << c.re << " im = " << c.im << endl;
return os;
}
my_complex my_complex::eek:perator+(const my_complex &r)
{
cout << "from member operator+" << endl;
return my_complex(re + r.re, im + r.im);
}
my_complex operator+(const my_complex &c1, const my_complex &c2)
{
cout << "from friend operator+" << endl;
my_complex c(c1.re + c2.re, c1.im + c2.im);
return c;
}
int main()
{
my_complex obj(1, 2);
cout << 5.0 + obj;
cout << obj + 5.0;
return 0;
}
The output of the above program under both VC++2005 Express Edition
and g++, is
from friend operator+
re = 6 im = 12
from member operator+
re = 6 im = 12
I thought ambiguity error would be generated for the expressions '5.0
+ obj' and 'obj + 5.0' because I have defined operator+() both as
member function and as friend function. But my understanding seems to
be wrong. Kindly clarify where I am going wrong.

The rule is actually fairly simple (in this case---I won't
pretend that overload resolution is simple in the absolute). In
the first case ("5.0 + obj"), the member operator cannot be
called. The rule is that a member function cannot be called on
a temporary which results from an implicit conversion. (Note
that "my_complex( 5.0 ) + obj" will call the member function.)
The reason for this rule is simple: allowing implcit conversions
here would be too error prone; the programmer expects member
functions to be called on a specific object.

In the second case, both functions can be called. The member
function is chosen, because it is non-const, and the object it
is being called on is non-const.

Note that the rule which applies in the first expression is
usually used to simulate the distinction between requiring an
lvalue or not for the operators. The rules aren't quite the
same, but the effects are similar enough: if a binary operator
requires an lvalue as its right argument, or a unary operator
requires an lvalue as its only argument, it is declared a member
(and thus, must be called on an existing object); if not, it is
declared as a free function (and thus all possible conversions
are taken into account). There are still some differences:
static_cast< double >( i ) += 3.2 is illegal, but static_cast<
my_complex >( i ) += 3.2 would pass. But it's close enough to
prevent casual errors.
 
S

subramanian100in

On Dec 3, 3:21 pm, "(e-mail address removed), India"
The rule is that a member function cannot be called on
a temporary which results from an implicit conversion. (Note
that "my_complex( 5.0 ) + obj" will call the member function.)
The reason for this rule is simple: allowing implcit conversions
here would be too error prone; the programmer expects member
functions to be called on a specific object.

Is my following understanding of the reason you have stated for the
above rule correct ?

While evaluating the expression 5.0 + obj, if implicit conversion is
allowed, then the compiler may not know which type it should convert
5.0 to.

Kindly clarify with an example.

Thanks
V.Subramanian
 
J

James Kanze

Is my following understanding of the reason you have stated for the
above rule correct ?
While evaluating the expression 5.0 + obj, if implicit
conversion is allowed, then the compiler may not know which
type it should convert 5.0 to.

That's something someone else (Victor, I think) pointed out, and
it is certainly a reason as well. My point was more along the
lines that member functions operate on a specific instance of
the object. As such, it doesn't make sense to call them on a
temporary, unless the user explicitly requests it.

You might argue that this is because you can't take the address
of a temporary, and the compiler needs to take the address of
the object in order to pass it the this pointer. But that's
only partially true; the compiler will accept a temporary here
if it is not the result of an implicit conversion.

As I said, when operator overloading is involved, the rule is to
use a member if the built-in operator requires an lvalue. Thus,
for example, you wouldn't want something like:

5.0 += obj ;

to compile, by converting 5.0 to my_complex, and doing the
operations on it. 5.0 += 1 doesn't compile, because the
built-in operator requires an lvalue. And the above won't
compile, even if my_complex defines +=, because the compiler
will not consider conversions for the member function. If it
did, then this would match a member operator+=( my_complex
const& ). By not considering conversions which result in a
temporary, the above will not compile.
 
S

subramanian100in

Kindly bear with me for asking the same question regarding 5.0 + obj.
I still am not able to understand the rule that a member function
cannot be called on a temporary which results from an implicit
conversion. why implicit conversion here is error-prone. Kindly give
an example.

Thanks
V.Subramanian
 
V

Victor Bazarov

Kindly bear with me for asking the same question regarding 5.0 + obj.
I still am not able to understand the rule that a member function
cannot be called on a temporary which results from an implicit
conversion. why implicit conversion here is error-prone. Kindly give
an example.

The answer is simple: because the Standard prohibits that. Please
kindly see Standard, [over.match.oper] (13.3.1.2 in the latest).

Temporaries are created when they are required, not on a whim of
the compiler. If you see the expression 1+2, and both 1 and 2 can
be converted to 'A', why don't they? Kindly use your head.

V
 
J

James Kanze

Kindly bear with me for asking the same question regarding 5.0 + obj.
I still am not able to understand the rule that a member function
cannot be called on a temporary which results from an implicit
conversion. why implicit conversion here is error-prone. Kindly give
an example.

In the case of 5.0 + obj, assuming a member operator+, it isn't.
In the more general case, it is; you can't write 5.0 +=
someDouble, and you don't want to be able to write 5.0 += obj.
In the case of double, the rules involve lvalue-ness. There's
no way of overloading on lvalue-ness for user defined types,
however, so another solution has been adopted: if you want to
support implicit conversions on the left hand operand, you make
the function global, and if you don't, you make it a member.
The rules allows you to have a choice.

The rule is also very relevant in expressions like a.f(). Here,
f() is a member function, designed to operate on an object. If
a isn't an object on which it can operate, without the rule, it
would operate on a temporary, which almost certainly isn't what
the programmer wanted.

Also, as Victor pointed out, how is the compiler to guess which
classes to look at? I hadn't considered the question before
Victor mentionned it, but I rather think that without something
like this rule, the compiler would not be implementable.
(Obviously, one can't say for sure until the alternative is
precisely specified.)
 
S

subramanian100in

consider the following:

#include <iostream>

using namespace std;

class Test
{
public:
Test(int arg = 0) : val(arg) { }

Test(const Test &rhs) : val(rhs.val) { }

Test operator+(const Test &rhs) const;

friend Test operator+(const Test &lhs,
const Test &rhs);

private:
int val;
};

Test Test::eek:perator+(const Test &rhs) const
{
cout << "from member operator+" << endl;
return Test(val + rhs.val);
}

Test operator+(const Test &lhs, const Test &rhs)
{
cout << "from friend operator+" << endl;

return Test(lhs.val + rhs.val);
}

int main()
{
Test obj(10);
Test temp(20);
Test result;

result = obj + temp;

return 0;
}

This generates ambiguity for the expression 'obj + temp' because the
Test::eek:perator+() is const. If it is non-const the friend function is
called. 'Test::eek:perator() const' function has 'this' pointer as
pointer to const object. This is mentioned earlier in this thread by
Abhishek Padmanabh.

From this I understand the following: Kindly correct me if my
understanding is wrong:

For the type 'Test', 'const Test &' is implemented by the compiler as
'const Test * const'. So the friend function becomes
operator+(const Test *const lhs,
const Test *const rhs);

'Test::eek:perator+(const Test &rhs) const' is internally implemented by
the compiler as
'Test::eek:perator+(const Test * const this,
const Test *const rhs);

Since the types and number of parameters for the friend function
operator+() and Test::eek:perator+()const member function are the same,
we get ambiguity error.

My doubt here: is the ambiguity generated due to the above internal
implementation of 'const Test &' as 'const Test *const' ? Or the
reasoning for ambiguity is different.

Kindly clarify.

Thanks
V.Subramanian
 
A

Abhishek Padmanabh

consider the following:

#include <iostream>

using namespace std;

class Test
{
public:
Test(int arg = 0) : val(arg) { }

Test(const Test &rhs) : val(rhs.val) { }

Test operator+(const Test &rhs) const;

friend Test operator+(const Test &lhs,
const Test &rhs);

private:
int val;

};

Test Test::eek:perator+(const Test &rhs) const
{
cout << "from member operator+" << endl;
return Test(val + rhs.val);

}

Test operator+(const Test &lhs, const Test &rhs)
{
cout << "from friend operator+" << endl;

return Test(lhs.val + rhs.val);

}

int main()
{
Test obj(10);
Test temp(20);
Test result;

result = obj + temp;

return 0;

}

This generates ambiguity for the expression 'obj + temp' because the
Test::eek:perator+() is const. If it is non-const the friend function is
called. 'Test::eek:perator() const' function has 'this' pointer as
pointer to const object. This is mentioned earlier in this thread by
Abhishek Padmanabh.

From this I understand the following: Kindly correct me if my
understanding is wrong:

For the type 'Test', 'const Test &' is implemented by the compiler as
'const Test * const'. So the friend function becomes
operator+(const Test *const lhs,
const Test *const rhs);

'Test::eek:perator+(const Test &rhs) const' is internally implemented by
the compiler as
'Test::eek:perator+(const Test * const this,
const Test *const rhs);

Since the types and number of parameters for the friend function
operator+() and Test::eek:perator+()const member function are the same,
we get ambiguity error.

My doubt here: is the ambiguity generated due to the above internal
implementation of 'const Test &' as 'const Test *const' ? Or the
reasoning for ambiguity is different.

The reasoning does not need you to go to the implementation of how
member functions look like or what references are, etc. etc.

It will be a repeatition of what has been already said but ... lets
see.

You have 2 statements:

1. cout << 5.0 + obj;
2. cout << obj + 5.0;

Let us see the second case first, since I think you are not clear on
the const thing. The object 'obj' is not a const object, right? So,
let us suppose, if you have two member functions of class my_complex,
named f(), where one is const and one is a non-const member. Let us
also suppose, you create two objects of type my_complex, one const and
another non-const. Like below:

#include <iostream>
using namespace std;
class my_complex
{
public:
my_complex(double r, double i = 10.0) : re(r), im(i) { }
my_complex(const my_complex &rc) : re(rc.re), im(rc.im) { }
void f() //non-const member function
{
std::cout << "hello" << std::endl;
}
void f() const //const member function
{
std::cout << "hello const" << std::endl;
}
private:
double re;
double im;
};
int main(){
my_complex obj(1, 2);
const my_complex obj2(1, 2);
obj.f(); //1
obj2.f(); //2
}

Let us take the case of first statement marked "//1". The type of the
object obj is non-const and f() is invoked. Which f() should be
invoked const version or non-const version? Let us assume the non-
const version. If that is the case, it would become impossible to ever
be able to call the non-const version of f() on any object of type
my_complex! Which would be a bad thing. Since my object is non-const,
I can have member functions that change the object and if that doesn't
happen, it will all become useless. The overloading of functions based
on their constness and non-constness would be a problem and hence
diallowed. But we know it is not disallowed. So, what should get
called? The non-const version of f()! Correct! Then what is the use of
the const member function overload? Simple, these const functions get
invoked when the type of the object, they are invoked upon are const -
that is our statement "//2"! Because if a non-const version got
called, it can mess up the const-ness of the const object changing its
state, its member variables.

If that is understood, proceed ahead or if not then go back to the
start of the previous para and keep repeating until it gets clear.
Now, let us assume that there was no non-const version of f() in
my_complex. So, my_complex would be:

class my_complex
{
public:
my_complex(double r, double i = 10.0) : re(r), im(i) { }
my_complex(const my_complex &rc) : re(rc.re), im(rc.im) { }
void f() const
{
std::cout << "hello const" << std::endl;
}
};

Now, what happens when statements //1 and //2 are called? //2 is clear
from the previous example, the const-version of f() gets called. But
what happens when f() is called over a non-const object? We don't have
a non-const version of f()! Well, the deal with cv-qualified member
functions is that they can be invoked on an object that is as cv-
qualified as the member function or less cv-qualified than the member
function. So, you can call const member functions with a non-const
object but that only happens in the absence of a non-const overload.
If a non-const overload is available, it is the better match.

This reasoning is related to what I first posted about the const-ness.
Let's go back to the statements 2: cout << obj + 5.0; What happens
here is that the second argument 5.0 takes the shape of a temporary
object using the conversion constructor you have. And since the first
argument 'obj' is non-const, the non-const member function is called.
Recall, that in the presence of a non-const match, when a function is
invoked on a non-const object, the same gets priority over the const
version (here our friend operator that takes the first argument as
const).

Now, as per my suggestion, if you made the member function const, now,
there are 2 possibilities. Because, the resolution (operator overload
invoked on non-const object), can happen either to the const member
function or the friend overload taking the first argument as const
my_complex& type. That is the source of this ambiguity.

Now, coming to the statement 1: cout << 5.0 + obj; - here, 5.0 gets
converted to a temporary object of type my_complex, implicitly. And
here the rule comes into play. That the member function will must
"never" get called when such implicit conversion happens to the first
argument (5.0, in this case). You can verify this by commenting out
the friend operator+ and keeping the member operator+ as const. Even
if the argument types would match after the implicit conversion, it
must not be called. So, in statement 1, the call always gets resolved
to the non-member friend operator overload. The simplest to understand
reason for me is, there is no object specified on which the member
should get invoked (before the implicit conversion, that is).

Sorry for the lengthy post. :(
 

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,757
Messages
2,569,543
Members
45,026
Latest member
camilin05

Latest Threads

Top