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:
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:
perator+() is const. If it is non-const the friend function is
called. 'Test:
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:
perator+(const Test &rhs) const' is internally implemented by
the compiler as
'Test:
perator+(const Test * const this,
const Test *const rhs);
Since the types and number of parameters for the friend function
operator+() and Test:
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.