Overloading unary operators
Unlike the operators you’ve seen so far, the positive (+), negative (-) and logical not (!) operators all are unary operators, which means they only operate on one operand. Because they only operate on the object they are applied to, typically unary operator overloads are implemented as member functions. All three operands are implemented in an identical manner.
Let’s take a look at how we’d implement operator- on the Cents class we used in a previous example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#include <iostream> class Cents { private: int m_cents; public: Cents(int cents) { m_cents = cents; } // Overload -Cents as a member function Cents operator-() const; int getCents() const { return m_cents; } }; // note: this function is a member function! Cents Cents::operator-() const { return Cents(-m_cents); } int main() { const Cents nickle(5); std::cout << "A nickle of debt is worth " << (-nickle).getCents() << " cents\n"; return 0; } |
This should be straightforward. Our overloaded negative operator (-) is a unary operator implemented as a member function, so it takes no parameters (it operates on the *this object). It returns a Cents object that is the negation of the original Cents value. Because operator- does not modify the Cents object, we can (and should) make it a const function (so it can be called on const Cents objects).
Note that there’s no confusion between the negative operator- and the minus operator- since they have a different number of parameters.
Here’s another example. The ! operator is the logical negation operator -- if an expression evaluates to “true”, operator! will return false, and vice-versa. We commonly see this applied to boolean variables to test whether they are true or not:
1 2 3 4 |
if (!isHappy) std::cout << "I am not happy!\n"; else std::cout << "I am so happy!\n"; |
For integers, 0 evaluates to false, and anything else to true, so operator! as applied to integers will return true for an integer value of 0 and false otherwise.
Extending the concept, we can say that operator! should evaluate to true if the state of the object is “false”, “zero”, or whatever the default initialization state is.
The following example shows an overload of both operator- and operator! for a user-defined Point class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#include <iostream> class Point { private: double m_x, m_y, m_z; public: Point(double x=0.0, double y=0.0, double z=0.0): m_x{x}, m_y{y}, m_z{z} { } // Convert a Point into its negative equivalent Point operator- () const; // Return true if the point is set at the origin bool operator! () const; double getX() const { return m_x; } double getY() const { return m_y; } double getZ() const { return m_z; } }; // Convert a Point into its negative equivalent Point Point::operator- () const { return Point(-m_x, -m_y, -m_z); } // Return true if the point is set at the origin, false otherwise bool Point::operator! () const { return (m_x == 0.0 && m_y == 0.0 && m_z == 0.0); } int main() { Point point{}; // use default constructor to set to (0.0, 0.0, 0.0) if (!point) std::cout << "point is set at the origin.\n"; else std::cout << "point is not set at the origin.\n"; return 0; } |
The overloaded operator! for this class returns the boolean value “true” if the Point is set to the default value at coordinate (0.0, 0.0, 0.0). Thus, the above code produces the result:
point is set at the origin.
Quiz time
1) Implement overloaded operator+ for the Point class.
![]() |
![]() |
![]() |
Question 1
1. Feedback
Could we just use 1 super example per chapter?
It is confusing to switch between Cents, Points, Fractions.
That way, students will remember your lessons properly and you can constantly re-use and upgrade the example in 1 single chapter.
When students learn <insert-name-for-super-example-for-Chapter-9>, they will associate that super example with overloading.
E.g. Chapter 9
The Points super example.
"It returns a Cents object that is the negation of the original Cents value. Because operator- does not modify the Cents object, we can (and should) make it a const function"
I don't understand. You return the negation of the original Cents value, therefore you're modifying the Cents objects; but you explicitly said this doesn't modify it. Could you explain please?
`a` was not modified. With the same reasoning, the original `Cents` object was not modified.
Hmmmm. In your operator! in the Point example you're comparing floating-point numbers to 0.0. I thought that was not a good idea because a) FP numbers shouldn't be checked for equality anyhow and b) there's no way to represent 0.0 precisely. Is this not so?
Is there any way to implement the following?
Yes, you can add a cast operator from `Point` to `bool`. We show it later in this chapter.
i did this and i thought output would be --- "A nickle of debt is worth hello -5 cents"
but the actual output is ---- " hello A nickle of debt is worth -5 cents"
can someone explain why did this happen?
whats the flow of control here?
It's undefined, the functions can be called in any order. If you need functions to be called in a specific order, call them one after the other.
So the way I understand this is that the std::cout part is an expressin, so it looks like this:
And there is no confusion which order it gets resolved in, its inner () first, but then when you get to (-nickle).getCents() it becomes getCents(operator-(nickle)).
So to me that looks ok as well because you have the inner function call and then the outer function call. I thought that undefined behavior happens if you expected function parameters to be evaluated in a special order or something along those lines. Am I missing something from this example? Where exactly is the undefined behavior occurring?
Because there are no overlapping modifications to a variable, it's not undefined behavior in this case.
Your parentheses are correct, but there's still a function call within that expression.
This expression will be evaluated from left to right, but the (4 + 5) can be evaluated at any point, even before the 1 is looked at. Evaluating (4 + 5) at the very beginning, or only when it's needed doesn't make a difference, because the (4 + 5) is independent of the rest, the result doesn't change.
For the same reason, `operator-` and `getCents()` can be called at any point. The order of evaluation is different from operator precedence and associativity.
Ok this makes perfect sense now! The issue wasn’t that he was making a function call there, but that the function call he was making had a “side effect”? in that it also did output. There isn’t a guarantee that the call is made in the order we want it to be made, and since it also outputs there’s the issue.
If it was just modifying a value, then we wouldn’t have noticed.
Thanks for the reply!
"Extending the concept, we can say that operator! should evaluate to true if the state of the object is “false”, “zero”, or whatever the default initialization state is."
What is the state of the object in the last example? I can't relate the state of the object to the example.
What I see is the condition (!point) in the if statement becomes true or false depending on the boolean value of true or false returned from the overloaded operator!
"state" is the same as "values" in this case. The default initialization state is when all members have their default value (0).
Hello,I have a question in your example. Before we call the operator- , the value of nickle is 5. However,when we call the operator-(),the value of nickle becomes -5. And why can you say the operator-() doesn't modify the object?
In my opinion , its behavior violated the const meaning.
Does my concept be wrong?
The value of `nickle` doesn't change. `operator-` creates a new `Cents` object with value -5. You can print `nickle.getCents()` in line 27 to verify this.
Is there any difference if this function is outside of the class or inside?
Functions defined inside the class are assumed to be inline. Other than that, I'm not aware of any differences.
I was wondering why not have the operator- () function be left in the class itself?
You could define it inline with the class definition (because it's trivial).
Okay!
what about returning just by reference? Not const reference.
Is the object returned an r-value? So you can't return it by reference?
Most of the time you don't want to let the caller override the returned object. Returning a non-const reference allows the caller to do just that.