A Summary of Operator Overloading

This summary uses the CoinMoney example in the coursepack. Variables m1, m2, etc are all assumed to be CoinMoney objects. The Standard Library output streams also provide some examples.

Basic idea

You overload an operator in C++ by defining a function for the operator. This function takes at least one argument of your own type, and returns a value of whatever type that you want.

You do this differently depending on whether the operator function is a member of your own type's class, or is a non-member function. A further variation is whether the arguments are of the same or different types. We'll do this first for non-member functions, then for member functions.

In schematic form, when you use a binary operator, there is a left-hand-side operand and a right-hand-side operand, and the whole expression has a value.

lhs op rhs --- has a value resulting from applying "op" to "lhs" and "rhs"

The operator function's first argument is the lhs operand, and the second is the rhs operand. The name of the operator function is "operator op", where op is the operator, such as '+', '*', etc.

Non-member operator functions

A non-member operator overloading function simply has the right name and does whatever you want. For example, suppose we want to add two CoinMoney objects and get a third CoinMoney object that has the sum of the nickles from the first two, etc. We define the function named operator+ that takes two arguments of CoinMoney type and returns a CoinMoney object with the correct values.

Let's use the example version of CoinMoney in which the member variables nickels, dimes, etc., are private.

CoinMoney operator+ (CoinMoney lhs, CoinMoney rhs)
{
CoinMoney sum;
sum.set_nickels(lhs.get_nickels() + rhs.get_nickels() );
... etc ...
return sum;
}

This function creates a new object, gets the nickels value from the lhs and rhs objects, and sets the nickels of the new object to their sum. Finally it returns the new object.

So now we can write:

m3 = m1 + m2;

The "m1 + m2" will be compiled into a call to our operator+ function that takes two CoinMoney objects as arguments. It returns an object containing the sum, whose values then get copied into m3. Because operator+ is just another function, the following two statements do exactly the same thing:

m3 = m1 + m2;

m3 = operator+ (m1, m2);

Explicitly calling operator functions is legal, and is sometimes done in special circumstances, but why bother?

Important: C++ does not allow you to define overloaded operators where both arguments are built-in types like int, double, etc. These operator definitions are fixed in the language. Otherwise, you could change integer addition into integer subtraction! Even C++ is not that crazy!


Left- and right-hand operands can be different types

At least one of the operator function parameters has to be of your type, but the other one can be of any other type, including a built-in type. For example:

CoinMoney operator* (CoinMoney lhs, double rhs)
{
CoinMoney product;
product.set_nickels( lhs.get_nickels() * rhs);
etc
}

This overload means that we can write:

m1 * 2.0;

This gives us a CoinMoney object that has double the number of each type of coin as m1.

Note that if we want to be able to write:

2.0 * m1;

We also have to define CoinMoney operator* (double lhs, CoinMoney rhs). The order of the argument types is different!

Avoiding reader/writer function calls

If we don't like having to go through reader/writer functions to get at the private members, we can do two things: Make the operator function a friend of the class, or make the operator function a member of the class.

Friend functions

A class can declare that a specific function is its friend. The friend now has access to the private members of the class. This is easy to do. Anywhere in the class declaration, put a statement of the form:

friend your_function_prototype;

For example:

class CoinMoney
{
... blah blah ...

friend CoinMoney operator+(CoinMoney lhs, CoinMoney rhs);

... blah blah ...
}

Now the operator function can be written with dot operators to get at the private member variables.

CoinMoney operator+ (CoinMoney lhs, CoinMoney rhs)
{
CoinMoney sum;
sum.nickels = lhs.nickels + rhs.nickels;
... etc ...
return sum;
}

Operator functions that are class member functions

If you write an operator function as a member function, then it automatically has access to all of the member variables and functions of the class. Friend declaration not needed! But the complication is that the left-hand-side operand becomes the "implicit argument" of the function - it is "this" object, the current one being worked on. So the member operator function has only one argument, the right-hand-side operand. For example, the overload operator function for + becomes:

class CoinMoney
{
... blah blah ...

CoinMoney operator+ (CoinMoney rhs) //member function
{
CoinMoney sum;
sum.nickels = nickels + rhs.nickels;
etc
return sum;
}
... blah blah ...
};

The naked "nickels" is the member variable in the left-hand-side operand, "this" object. The function has only one parameter, the right-hand-side operand. Because this function is a member of the class, it has direct access to the member variables in "this" current object, and dot access to the member variables in the other objects in the same class.

Since a member operator function is just an ordinary member function, the following statements do the same thing:

m3 = m1 + m2;

m3 = m1.operator+ (m2);

The member version of an operator function is called to work on the left-hand operand, with the right-hand operand being the function argument. Again, calling operator functions explicitly is legal, but rare, and usually pointless.

Left-hand operand for member operator functions must be the class type

As with non-member operator overload functions, you don't have to have both arguments be the same type. However, by definition, the left-hand operand for a member operator function must be an object of the class that the function is a member of.

For example, suppose we wanted to be able to multiply CoinMoney objects by doubles as in the above example. We can define operator* as a member function only if a CoinMoney object is the left-hand operand, but not if a double is the left-hand operand:

That is, CoinMoney operator* (double x) can be defined as a member function of CoinMoney, and so

m1.operator* (2.5);

is a legal call.

But you can't make a function a member of a class and then apply it as if it were a member of a different class. For example, suppose foo was member function of the class Thing.

class Thing
{
void foo() {... blah blah ...}
};

And we try to apply foo to the CoinMoney class which has no member function named foo:

m1.foo();

The compiler will say that there is no such member function for the class that m1 belongs to. So if operator* is defined in the CoinMoney class,

my_double_variable.operator* (m1)

is illegal.

What do you do in such cases? Simple: define this version of the operator overload using a non-member function. So for multiplying doubles and CoinMoney, we would have the following:

class CoinMoney
{
... blah blah ...
// member function:
CoinMoney operator+ (double rhs)
{
... blah blah ...
}
... blah blah ...
};

// non-member function:
CoinMoney operator+ (double lhs, CoinMoney rhs)
{
... blah blah ...
}

The member function handles

m2 = m1 * my_double_variable;

and the non-member function handles

m2 = my_double_variable * m1;

Which operators can and should be overloaded?

Almost all of the operators can be overloaded. But that doesn't mean you should overload them! Good OOP practice is to overload operators for a class only when they make obvious sense. For example, what would less-than mean for CoinMoney?

m1 < m2

There are several reasonable ways that one collection of coins could be considered to be less than another - total number, total value, number of highest value coin, even total weight! Finally, there is no clue what some operators might mean, such as:

(m1 % m2++) | m3

Note that you don't have to define both "ways" for an overloaded operator. For example, maybe you want half the coins in a CoinMoney object:

m1 / 2.0

This might make sense, but why would you want to divide by a CoinMoney object?

2.0 / m1 ???

You only have to define the versions of the overloads that make sense and that you want to be able to use.


How do I overload the output operator to output objects of my own type?

Just like any other operator, but you have to get certain things right. Here's the basic pattern to allow you to output CoinMoney objects like any other type, as in

cout << m1 << "Hello!" << my_int; // etc

ostream& operator<< (ostream& os, CoinMoney x)
{
os << /* whatever you want to output about x */;
return os;
}

Here are the things you have to get right:

1. The operator<< function can't be a member of your own class, because the left-hand operand is an ostream object.

2. The first parameter has to be a reference-type parameter because you want os to be the very same stream object that the operator is applying to, and not a copy of it. Inside the function, os is an alias, another name, for the original cout object.

3. The return type has to be a reference to an ostream object, so that each application of << will produce the same ostream object that was originally on the left-hand side, so the next << will take it as its left-hand operand. This is why you can cascade the output operator.

4. You have to be sure to return the ostream parameter object so that the cascading in #3 will work.

See Winston, p. 199, "How to overload the output operator" for more discussion.

The ostream class includes an overload of operator<< for every built-in type, as in:

ostream& operator<< (ostream& os, int x)
{/* output an integer */}

ostream& operator<< (ostream& os, double x)
{/* output a double */}

ostream& operator<< (ostream& os, char * x)
{/* output a C string */}

This is why output using the iostream library is type-safe - the compiler will make sure the right output function is called for the type of object you are outputting.

What about unary operators?

Overloading unary operators works the same way, except there is only one parameter to the operator function when it is a non-member, and no parameters when it is a member. For example, suppose we wanted the negation operator (!) to return true if a CoinMoney object was empty (all fields zero), and false otherwise. We just define a nonmember function:

bool operator! (CoinMoney m)
{
return (
m.get_nickels() == 0 &&
m.get_dimes() == 0 &&
m.get_quarters() == 0
);
}

or a member function:

bool operator! ()
{
return (
nickels == 0 &&
dimes == 0 &&
quarters == 0
);
}

The istream and ostream classes typically have a similar definition of operator! to implement the test of whether a stream is good by checking the stream object, as in:

if(!my_file) {/* oops! something is wrong! */}


Conversion operators.

The compiler sometimes needs convert an object of one type to another. In the CoinMoney example, we might want to treat a CoinMoney object as a single numeric value, say as a double, so we could write:

tax = .06 * m1;

where we want to multiply .06 times the value of m1. If we tell the compiler how to turn a CoinMoney object into a double, then the compiler can just generate the rest of the code like it usually does. We would write (say as a member function):

operator double ()
{
return value();
}


Conversion operators are not used very often, because you can get nasty surprises. Notice for example that if we had both this conversion operator and the operator* (double, CoinMoney) we now have an ambiguity in how the compiler should analyze the above tax statement. So use these very sparingly.

The istream and ostream classes typically contain a conversion operator to convert a istream or ostream object into a bool, so that you can write:

if(my_input_file) {/* everything is good! */}

You aren't testing for whether the humongous istream object is true or nonzero - it's a hundred+ bytes jammed full of data! But the function

operator bool (istream& is) {whatever}

computes a true/false value based on the stream state bits, thereby allowing you to treat the stream object as a single true/false value.

For the curious: A tidbit about object conversions

Sometimes a constructor function plays the role of a conversion function.
For example, consider the following code sketch:

class Thing
{
blah blah
};

class Glob
{
Glob(Thing t);
blah blah
};

void foo(Glob g);

....
Thing my_thing;
...
foo(my_thing);
...

You can't call foo with a Thing as an argument; foo requires a Glob. But the compiler cleverly notices that it can construct a Glob from a Thing, so it compiles the call as if the programmer had written:

Glob my_glob(my_thing);

foo (my_glob);

Often this is exactly what you want. Sometimes it is a source of mysterious errors. The keyword "explicit" is used to prevent this use of a constructor.