Click to See Complete Forum and Search --> : Overloading an operator... I'm really not getting it.


dreamfluid
January 29th, 2006, 06:49 AM
I hope I won't get flamed for this, but after 2 books "Small C++" by Deitel & Deitel, "C++ for java programmers" - Mark Weiss and 2 tutorials on the net, I still don't understand the syntaxes for overloading an operator and how to use them..
ostream &operator<<( ostream &output, const PhoneNumber &number )
{
output << "(" << number.areaCode << ") "
<< number.exchange << "-" << number.line;
return output;
Fraction operator+(const Fraction &rhs)
{
Fraction temp;
temp.den = this->den * rhs.den;
temp.num = rhs.den * this->num +
this->den * rhs.num;
return temp;
}Aha! This says very much to me..
bool operaotr==(const Card& rhs) const
{
return suit == rhs.suit && value == rhs.value;
}

Card aceSpades(Card::spade, 1);what's with the &rhs's everywhere? Is aceSpades line supposed to have something to do with the overloading?

I've looked around some more on the net and all they do is take examples from a physisist's physics calculating class or something and spew code all over and then they say: "See? That wasn't so hard", and I'm like, what the hell happened here? What is what?

There is no place where they explain step by step how to initiate it and then use it....

ovidiucucu
January 29th, 2006, 08:35 AM
[ Moved thread ]

laserlight
January 29th, 2006, 09:47 AM
You could take a look at an FAQ concerning operator overloading (http://www.parashift.com/c++-faq-lite/operator-overloading.html).

rhs presumably stands for "right hand side", and so refers to the value on the right hand side of a dyadic operator.

Is aceSpades line supposed to have something to do with the overloading?
Probably not, it looks like it is meant to instantiate a Card object.

Rich2189
January 29th, 2006, 11:23 AM
You could take a look at an FAQ concerning operator overloading (http://www.parashift.com/c++-faq-lite/operator-overloading.html).

rhs presumably stands for "right hand side", and so refers to the value on the right hand side of a dyadic operator.


Probably not, it looks like it is meant to instantiate a Card object.


it does stand for right hand side :), well thats the convension that i use. Below is a hopefully easy examply.



#include <iostream>

using namespace std;

class Counter
{
public:
Counter();
Counter(int initialValue);
~Counter(){};
int GetItsVal() const {return itsVal;}
void SetItsVal(int x) {itsVal = x;}
Counter operator+ (const Counter &);

private:
int itsVal;

};

Counter::Counter(int initialValue):itsVal(initialValue)
{}

Counter::Counter():itsVal(0)
{}

Counter Counter::operator+(const Counter & rhs)
{
return Counter(itsVal + rhs.GetItsVal());
}

int main()
{

Counter varOne(2),varTwo(4),varThree;
varThree = varOne + varTwo;

cout << "varOne: " << varOne.GetItsVal() << endl;
cout << "varTwo: " << varTwo.GetItsVal() << endl;
cout << "varThree: " << varThree.GetItsVal() << endl;

system("PAUSE");
return 0;

}



The overloaded + operator enables you to add the objects together. when the + operator is used it passes in the counter variable on the right and adds it to the counter object which is calling the function. The &rhs means it is passed by reference. I always think of it as a member function called "+" so nomally you would do functionName(params), but this time its +(params) without the parenthisis.

In the case of your code it tells the object what to do when two objects are compared. It returns true if they are equal and false if they are not.

Rich

treuss
January 29th, 2006, 01:26 PM
Let's try to bring some order in this: ostream &operator<<( ostream &output, const PhoneNumber &number )
{
output << "(" << number.areaCode << ") "
<< number.exchange << "-" << number.line;
return output;
Fraction operator+(const Fraction &rhs)
{
Fraction temp;
temp.den = this->den * rhs.den;
temp.num = rhs.den * this->num +
this->den * rhs.num;
return temp;
}These examples you quote there are quite different. The first one overloads the operator <<, making it possible to print out an object of class PhoneNumber. The second one deals with the operator + for two objects of type Fraction.
What makes these examples difference, is that the second one is a member function of class Fraction, while the first one is not a member function. That's why the first example takes two arguments (i.e. in the statement "cout << number;", cout would be the first and number the second argument), while the second example takes only one argument (i.e. in the statement "a+b;" a would be the object for which the function is called (the left hand side (lhs)) and be would be the argument (the right hand side (rhs)).

Before overloading operators, it is good to know what you want to achieve. One of the simplest examples is that you are writing a class, where you want to make objects comparable by using the operators ==, != and maybe <, >. So let's use the card example:
enum Suite {Clubs, Spade, Heart, Diamond};

class Card
{
private:
Suite suite;
int value;
public:
Card( Suite s, int v ) : suite( s ), value( v ) {};
bool operator==( const Card& rhs ) { return rhs.suite==suite && rhs.value==value; };
bool operator!=( const Card& rhs ) { return rhs.suite!=suite || rhs.value!=value; };
};The class definition defines a class Card. Objects of the class have a suite and a value, e.g Spade and 7.
The class defines the operator== and !=, so now it is easy for code using objects of class Card to compare two such objects. In the example below:
Card S7(Spade, 7);
Card H7(Heart, 7);
If (S7 == H7 ) { // the cards are the same...
The operator is used. More precisely, the operator is use on the object S7, and H7 is passed as argument. In other words, the if statement could be written as if (S7.operator==( H7 )) { // ...The function definition simply defines, when two cards are equal, i.e. if they have equal suite and equal value.

NMTop40
January 29th, 2006, 03:18 PM
but here operator+ is defined wrong as I so often see it.

laserlight
January 29th, 2006, 08:59 PM
but here operator+ is defined wrong as I so often see it.
hmm... yes, I would think so too. Perhaps defining operator+= as a member than defining a global operator+ in terms of the operator+= would be better?
void Counter::operator+=(const Counter& rhs)
{
itsVal += rhs.itsVal;
}

Counter operator+(Counter lhs, const Counter& rhs)
{
lhs += rhs;
return lhs;
}

exterminator
January 30th, 2006, 12:32 AM
Do not consider operator overloading anything other than a simple function. This helps!!!

Consider them normal functions and just look at the way they are used for the fundamental types or the types for which these are already defined. The arithmetic operators work on two operands, for ex, +/-/* etc. Now since there are two things that they work on the function needs to know what those two things(operands) are?

So, we pass them as arguments to the function. For member functions we show only one argument because of the implicit "this" argument. So, member overloads work on "this" and the second argument commonly known as "rhs". For non-member overloads, there is no "this" involved hence you give two arguments. The operators would work for your types as the same way they work with the pre-defined types (like say int or double).. now they will work for your classes as well. That's it! (there are also operators that work on just one operand ++, --)

That's all there is in Operator overloading 101 - the basics... Now, go ahead with whatever you want to read up higher on them... your basics is done.. operator overloading is not something more than mere functions... Hope this helps. Regards.

treuss
January 30th, 2006, 03:12 AM
but here operator+ is defined wrong as I so often see it.Could you elaborate? I don't see anything wrong with how it's defined. Thanks!

PadexArt
January 30th, 2006, 04:05 AM
Could you elaborate? I don't see anything wrong with how it's defined. Thanks!

You should think how the + operator is used: b+c. b+c does not modify neither b nor c so you the standard requires you to declare it like this:


Counter operator+(const Counter& lhs, const Counter& rhs)
{
Counter result;

// + implementation goes here

return result;
}


Using the Counter operator+(Counter lhs, const Counter& rhs) will work as well. The problem in this case is ( aside from not conforming to the C++ standard) that the copy constructor is invoked. For small objects this might not be significant but it can cause serious problems for bigger ones.

treuss
January 30th, 2006, 04:18 AM
You should think how the + operator is used: b+c. b+c does not modify neither b nor c so you the standard requires you to declare it like this.I still don't see, what's wrong with declaring it as a function member? The implementation above does not modify the function member. The only mistake I see now, is that it is not declared as Counter operator+ (const Counter &) const;

laserlight
January 30th, 2006, 04:23 AM
I don't see anything wrong with how it's defined.
Perhaps not wrong, but merely with an unexpected limitation.
We might want to do something like:
a = 1 + b;
If operator+ is a member of the class type, then this will fail. On the other hand, if operator+ was global, then 1 would be implicitly converted to the class type required (presumably an implicit constructor that accepts an int, or an operator int() is provided).

laserlight
January 30th, 2006, 04:31 AM
the standard requires you to declare it like this
hmm... I've been looking for direction on whether to have both argument const T& or to allow the first argument to pass by value, but my search was not fruitful. Now that you have confirmed my suspicions, could you point me to the section in the standard that has this mandate?

The problem in this case is ( aside from not conforming to the C++ standard) that the copy constructor is invoked. For small objects this might not be significant but it can cause serious problems for bigger ones.
Ah, but here's the thing. The implementation of the operator might invoke the copy constructor, so in such a case there would be no advantage gained by passing both arguments by reference. For example:
T operator+(const T& lhs, const T& rhs) {
T temp(lhs);
temp += rhs;
return temp;
}
would be logically equivalent to:
T operator+(T lhs, const T& rhs) {
lhs += rhs;
return lhs;
}
except that the latter does not use a temporary.

PadexArt
January 30th, 2006, 04:51 AM
Ah, but here's the thing. The implementation of the operator might invoke the copy constructor, so in such a case there would be no advantage gained by passing both arguments by reference.

no, they aren't.

The T operator+(T lhs, const T& rhs) implementation results in 2 calls for the copy constructor: 1 when the l argument is received and 1 when the result is returned.

The T operator+(const T& lhs, const T& rhs) implementation results in just 1 call for the copy constructor: when the result is returned.

laserlight
January 30th, 2006, 05:03 AM
The T operator+(const T& lhs, const T& rhs) implementation results in just 1 call for the copy constructor: when the result is returned.
That's assuming that operator+ is a friend function and the raw implementation of the operator lies within that function. In my example, a temporary is instantiated by passing the first argument to operator+ to the copy constructor. Then temporary's operator+= is invoked with the second argument to operator+ as the argument. In this case, operator+= implemented as a member function containing the raw implemention.

googler
January 30th, 2006, 05:08 AM
A binary operator shall be implemented either by a non-static member function (9.3) with one parameter or by a nonmember function with two parameters. Thus, for any binary operator @, x@y can be interpreted as either x.operator@(y) or operator@(x,y). If both forms of the operator function have been declared, the rules in 13.3.1.2 determine which, if any, interpretation is used.

So
T T::operator+(const T&);
T T::operator+(const T&) const;
T ::operator+(const T&, const T&);
T ::operator+(T, const T&);
are all valid overloads.

PadexArt
January 30th, 2006, 05:13 AM
I still don't see, what's wrong with declaring it as a function member? The implementation above does not modify the function member. The only mistake I see now, is that it is not declared as.
The member version is fine. I was reffering to the friend version.

That's assuming that operator+ is a friend function and the raw implementation of the operator lies within that function. In my example, a temporary is instantiated by passing the first argument to operator+ to the copy constructor. Then temporary's operator+= is invoked with the second argument to operator+ as the argument. In this case, operator+= implemented as a member function containing the raw implemention.

I'm sorry but I'm not quite following your point. What does that operator+= has to do with how the operator+ is defined? :confused:

PadexArt
January 30th, 2006, 05:31 AM
Here is the thing: (this is operator+ implemented in terms of operator+=)

I can understand that but you have operator+(const T& lhs, const T& rhs) and not operator+(T lhs, const T& rhs). What I am trying to say is that the 2nd form calls the copy constructor 1 extra time, regardless of how it is implemented.

EDIT: what happened to your post? :confused:

laserlight
January 30th, 2006, 05:36 AM
The member version is fine. I was reffering to the friend version.
Gah, where? I cant find any of the posts in this thread containing operator+ as a friend function. If anything the implementation of the member operator+ that dreamfluid doesnt work at all, but that has to do with it being implemented as if it were global (temp is never assigned to *this in any way), yet defined as if it were a member.

What does that operator+= has to do with how the operator+ is defined?
i.e. operator+= is used to implement operator+

PadexArt
January 30th, 2006, 05:41 AM
Gah, where? I cant find any of the posts in this thread containing operator+ as a friend function.
Well, one would expect that an operator defined as a global function is also a friend. :)

i.e. operator+= is used to implement operator+
And what does that have to do with sending the 1st argument of the operator+ by value? :)

laserlight
January 30th, 2006, 05:47 AM
What I am trying to say is that the 2nd form calls the copy constructor 1 extra time, regardless of how it is implemented.
Yes, it is obvious that the 2nd form (T operator+(T, const T&) ) calls the copy constructor at least twice. On my part, what I'm trying to point out is that in the 1st form (T operator+(const T&, constT&) ) if operator+ is implemented in terms of operator+=, it may be useful to use the copy constructor to create a temporary copy of one of the arguments, and then apply operator+= on that temporary, returning the temporary by value.

In such a case, the copy constructor would be called twice, i.e. as many times as in the 1st form. If this is the case, then it may well be better to pass one of the arguments by value (i.e. use the 2nd form) and hence not have to explicitly instantiate a temporary.

Now, if indeed the C++ Standard mandates the 1st form (to work with the standard library?) as a convention, then choosing the 2nd form may not be wise; this is why I would like to know where in the standard is this mentioned. googler's post just shows the forms that are legal, but says nothing about convention that may be useful when working with the standard library.

exterminator
January 30th, 2006, 06:00 AM
EDIT: what happened to your post? :confused:Actually I was composing the post and then got to talking with a colleague.. in the meanwhile quite a few posts got updated and I thought I reconsider... and you quoted me ...anyways.. here it is (with a little changes/additions):

I think there is an item by Scott Meyers regarding this as well... what I could make out of it was two things:
1. You would need to only maintain operator+= for your classes where operator+ is implemented in terms of operator+=.
2. There could be a gain in performance owing to RVO.

Here is the thing: (this is operator+ implemented in terms of operator+=)//just need to maintain this function...
template<typename T>
T& operator+=(const T& rhs){
//do stuff..
return *this;
}
//no maintenance required... once += is maintained well
template<typename T>
const T operator+(const T& lhs, const T& rhs){
return T(lhs)+=rhs; //RVO
}

//first argument by value
template<typename T>
const T operator+(T lhs, const T& rhs){ //copy constructor called as well
return lhs+=rhs; //no RVO as the return object is named
}I agree that there is no problem with having overloads as what treuss is trying to do and I also agree what PadexArt says about copy constructions.

PadexArt: What does that operator+= has to do with how the operator+ is defined?
laerlight: i.e. operator+= is used to implement operator+
PadexArt: And what does that have to do with sending the 1st argument of the operator+ by value?
Well, hope I am up-to-post while I post my post... :)

operator+= has nothing to do with how operator+ is implemented... they can be completely independently defined as well... but don't you think that a kind of rework when there is an alternative way that can give you an extra bit of performance and also keep your worries away while maintaining the code that you only need to modify operator+= when you need to.. You can simply leave the operator+ as it is.. I would consider it as an advantage and would try to exploit it.

As far as sending the first argument by value is concerned look at the second version of operator+ that I have quoted above. Even if operator+ is or is not defined in terms of operator+= what happens is the object gets "named" and hence all possibilities of "un-named RVO" are lost. There could be gains on certain compilers due to "named RVO" (I guess VS 7.0 and above compilers do that, I am not sure). In that case (named RVO), both would be equivalent. And the conclusion that we can derive is that however there (while comparing first argument passed as T or const T&) may be no performance boost on such compilers (with both named and unnamed RVO) still you get the advantage related to code maintainance (while implementing operator+ in terms of operator+=)

Hope I have been clear in what I said.. Regards.

googler
January 30th, 2006, 06:04 AM
What I am trying to say is that the 2nd form calls the copy constructor 1 extra time, regardless of how it is implemented.
The form T ::operator+(const T& lhs, const T& rhs); always has to call some constructor for T in order to generate a temporary local to be returned by the function, even if that constructor is not the copy constructor. Depending on other implementation details of T, it may be adventageous to use the copy constructor. For instance, if all other constructors take a complicated set of initial parameters. In other circumstances, the copy constructor may be "heavier" than other constructors. So it's not as clear cut as you make it out to be, and it may be reasonable to generate a copy of T by passing it by value.

laserlight
January 30th, 2006, 06:23 AM
There could be a gain in performance owing to RVO.
hmm... good point, but my example could not do what yours does since it declares operator+= returning void. It seems that need to revise my thinking a little :)

exterminator
January 30th, 2006, 07:27 AM
hmm... good point, but my example could not do what yours does since it declares operator+= returning void. It seems that need to revise my thinking a little :)
You should not return void for the operators.. why? Just for the sake of demonstrating this need.. I wrote the following code on Comeau and compiled it..#include<ostream>
using namespace std;
class A{
public:
int a;
void operator+=(const A& rhs){
this->a = this->a+rhs.a;
}
explicit A(int i):a(i){}
};

ostream& operator<< (ostream& os, const A& obj){
os << obj.a;
return os;
}

int main(){
A obj1(10);
A obj2(20);
int i=10;
obj1+=obj2;
cout << (i+=2);
cout << (obj1+=obj2);
return 0;
}The compile error that I get is:
"ComeauTest.c", line 23: error: no operator "<<" matches these operands
operand types are: std::ostream << void
cout << (obj1+=obj2);
^

1 error detected in the compilation of "ComeauTest.c".Note that this for int does not fail and the overloading should be done along these lines.. ( i.e. no errors for cout << (i+=2); ). Hope this helps. Regards.

laserlight
January 30th, 2006, 09:48 AM
Note that this for int does not fail and the overloading should be done along these lines.. ( i.e. no errors for cout << (i+=2); ).
Yeah, it makes sense to keep the behaviour similiar to that of a primitive type with similiar properties.