Overloading the comparison operators is comparatively simple (see what I did there?), as they follow the same patterns as we’ve seen in overloading other operators.
Because the comparison operators are all binary operators that do not modify their left operands, we will make our overloaded comparison operators friend functions.
Here’s an example Car class with an overloaded operator== and operator!=.
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 |
#include <iostream> #include <string> class Car { private: std::string m_make; std::string m_model; public: Car(const std::string& make, const std::string& model) : m_make{ make }, m_model{ model } { } friend bool operator== (const Car &c1, const Car &c2); friend bool operator!= (const Car &c1, const Car &c2); }; bool operator== (const Car &c1, const Car &c2) { return (c1.m_make == c2.m_make && c1.m_model == c2.m_model); } bool operator!= (const Car &c1, const Car &c2) { return !(c1 == c2); } int main() { Car corolla{ "Toyota", "Corolla" }; Car camry{ "Toyota", "Camry" }; if (corolla == camry) std::cout << "a Corolla and Camry are the same.\n"; if (corolla != camry) std::cout << "a Corolla and Camry are not the same.\n"; return 0; } |
The code here should be straightforward. Because the result of operator!= is the opposite of operator==, we define operator!= in terms of operator==, which helps keep things simpler, more error free, and reduces the amount of code we have to write.
What about operator< and operator>? What would it mean for a Car to be greater or less than another Car? We typically don’t think about cars this way. Since the results of operator< and operator> would not be immediately intuitive, it may be better to leave these operators undefined.
Recommendation: Don’t define overloaded operators that don’t make sense for your class.
However, there is one common exception to the above recommendation. What if we wanted to sort a list of Cars? In such a case, we might want to overload the comparison operators to return the member (or members) you’re most likely to want to sort on. For example, an overloaded operator< for Cars might sort based on make and model alphabetically.
Some of the container classes in the standard library (classes that hold sets of other classes) require an overloaded operator< so they can keep the elements sorted.
Here’s a different example with an overloaded operator>, operator<, operator>=, and operator<=:
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 48 49 50 51 52 53 54 55 56 |
#include <iostream> class Cents { private: int m_cents; public: Cents(int cents) : m_cents{ cents } {} friend bool operator> (const Cents &c1, const Cents &c2); friend bool operator<= (const Cents &c1, const Cents &c2); friend bool operator< (const Cents &c1, const Cents &c2); friend bool operator>= (const Cents &c1, const Cents &c2); }; bool operator> (const Cents &c1, const Cents &c2) { return c1.m_cents > c2.m_cents; } bool operator>= (const Cents &c1, const Cents &c2) { return c1.m_cents >= c2.m_cents; } bool operator< (const Cents &c1, const Cents &c2) { return c1.m_cents < c2.m_cents; } bool operator<= (const Cents &c1, const Cents &c2) { return c1.m_cents <= c2.m_cents; } int main() { Cents dime{ 10 }; Cents nickel{ 5 }; if (nickel > dime) std::cout << "a nickel is greater than a dime.\n"; if (nickel >= dime) std::cout << "a nickel is greater than or equal to a dime.\n"; if (nickel < dime) std::cout << "a dime is greater than a nickel.\n"; if (nickel <= dime) std::cout << "a dime is greater than or equal to a nickel.\n"; return 0; } |
This is also pretty straightforward.
Note that there is some redundancy here as well. operator> and operator<= are logical opposites, so one could be defined in terms of the other. operator< and operator>= are also logical opposites, and one could be defined in terms of the other. In this case, I chose not to do so because the function definitions are so simple, and the comparison operator in the function name line up nicely with the comparison operator in the return statement.
Quiz time
1) For the Cents example above, rewrite operators < and <= in terms of other overloaded operators.
2) Add an overloaded operator<< and operator< to the Car class at the top of the lesson so that the following program compiles:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <algorithm> #include <iostream> #include <string> #include <vector> int main() { std::vector<Car> cars{ { "Toyota", "Corolla" }, { "Honda", "Accord" }, { "Toyota", "Camry" }, { "Honda", "Civic" } }; std::sort(cars.begin(), cars.end()); // requires an overloaded operator< for (const auto& car : cars) std::cout << car << '\n'; // requires an overloaded operator<< return 0; } |
This program should produce the following output:
(Honda, Accord) (Honda, Civic) (Toyota, Camry) (Toyota, Corolla)
If you need a refresher on std::sort, we talk about it in lesson 9.4 -- Sorting an array using selection sort.
![]() |
![]() |
![]() |
hi there,
Are these the same? I find it quite confusing.
Thanks.
Question 2 Had to look at the solution...
Greetings! Am still loving this site, I'm really hooked on this stuff! I had a question regarding question 2 of the end of chapter quiz. Another person already commented that the most concise solution to overloading operator< was to implement the following return statement;
Initially I had a similar solution;
However my program kept crashing with an "invalid operator" error. I'm trying to understand both why my program kept crashing due to using operator|| in this statement and also why my statement wouldn't work logically. It seems to me that if the first half of the statement is true, true is immediately returned to the caller. However if the first half of the statement is not true, then the second half of the statement is evaluated. It would seem to me that this should work as intended and not produce any errors or crashes. Any feedback on this would be appreciated, thanks in advance.
James
> It seems to me that if the first half of the statement is true, true is immediately returned to the caller. However if the first half of the statement is not true, then the second half of the statement is evaluated.
That's correct.
Your operator has to satisfy the "comp" function of Compare https://en.cppreference.com/w/cpp/named_req/Compare
Specifically, "If comp(a,b)==true then comp(b,a)==false"
Your operator doesn't satisfy this requirement. Take this data for example
comp(a, b)==true (Because a.m_make<b.m_make)
but
comp(b, a)==true (Because b.m_model<a.m_model)
So, @a is ordered before @b, but @b is ordered before @a.
1. Query
Can you lambda an overloaded operator?
2. Quiz
My version of the operator< overload was a bit simpler (in my view, anyway). Is there a reason why it should be done one way vs. the other?
Thanks,
Lee
You're creating 2 new strings every time `operator<` is used (Because you're using `std::string + std::string`), that's expensive.
I think the most concise way would be to write
That's a nice solution indeed. I was about to update the lesson when I remembered that `std::string` comparisons use `compare()`, which means comparing m_make with `<` AND `==` is wasteful, as `compare()` performs both operations at once. I don't want to use `compare()` in the solution because it hasn't been covered, and I don't want to update the solution when I already know it's not good, so it will stay as-is.
In the last quiz question, i don't understand why do we have to "friend " the bool operator <. I mean since the operator is defined inside the class, it already has access to all its members so why do we have to friend it? I know it doesn't work without it, but why?
By making the function a `friend`, it is not considered to be a member function. This is the same as defining the function outside of the class. Without the `friend`, the operator would be a member function of the `class`, but then it would have 3 parameters (this, Car, Car) and no longer be a valid operator.
Follow up question: I don't understand the fact that being a member function makes you have 3 parameters, but not being part of it(except 'friend') makes you have only two parameters.
All non-static member functions have a `this` parameter, see lesson 8.8. If you make a function a `friend`, it's not a member function so it doesn't have a `this` parameter. If you don't make a function a `friend`, it is a member function and has the `this` parameter, so you can only have the right operand of the operator as an actual parameter. The left operand is `this`.
In the quiz I think friend is redundant. Should friend be removed where direct member access is no longer needed?
This implementation without ! also seems to work.
Thanks
Yes those `friend`s were redundant, I've removed them from the solution. Thanks!
Hi Alex, and nascardriver!!
Thank You for sharing knowledge, I learned a lot till now.
I was doing Quiz Question 2, I came across the problem what if we've to sort the cars on the basis of 3 variable, namely m_make, m_model, and m_name(I defined third member variable as m_name). How we will use std::sort function ?
Here is my modified Car class:
class Car
{
private:
std::string m_make;
std::string m_model;
std::string m_name; // new member variable
And, Initialy, vector that hold all cars:
std::vector<Car> cars{
{ "Toyota", "Corolla", "B" },
{ "Honda", "Accord", "Z" },
{ "Toyota", "Camry", "B" },
{ "Honda", "Civic", "D" },
{ "Toyota", "Corolla", "A" },
{ "Honda", "Accord", "A"},
{ "Toyota", "Caary", "A" },
{ "Honda", "Civic", "A" }
};
How we overload operator< so that std::sort will sort on all three variable:
friend bool operator<(const Car &c1, const Car &c2)
{
// How we will return a bool based on three member
variable.
}
Output will be:
Honda, Accord, A
Honda, Accord, Z
Honda, Civic, A
Honda, Civic, D
Toyota, Caary, A
Toyota, Camry, B
Toyota, Corolla, A
Toyota, Corolla, B
Hi!
You can just nest the if-statement deeper
Hi!
The std::vector<Car> variable in the program of the quizz 2 is named "cars", but it's named "v" in the solution of the same quizz. I think they should be named the same for consistency sake.
Also, some comparisons in the first example of the lesson don't have a space after the first operand. e. g. (c1== c2) instead of (c1 == c2).
Thanks!
Should be all fixed now, thanks!
We have two objects to compare with each other in every example in this chapter
However, in quiz 2, we have only one object(vector array). How could it compare with itself?
In line 56 requires an overloaded Car::operator<, Why not others?
I slightly modified the quiz 2 as shown below.
What is actually passed from the caller to Car::operator<()?
Could you please explain how my modified program works?
The output:
c1.m_make: Honda
c2.m_make: Toyota
c1.m_model: Accord
c2.m_model: Corolla
c1.m_make: Toyota
c2.m_make: Honda
c1.m_model: Corolla
c2.m_model: Accord
(Honda, Accord)
(Toyota, Corolla)
`std::sort` uses `operator<` to compare the elements of the vector. A < operator is all that's needed to sort lists, as you've seen in the lesson about sorting. There appears to be some general confusion about how `std::sort` works. I'll see if I can improve on the lessons introducing it. Update: I added a note for advanced readers to section "Using std::sort to custom sort" of lesson 6.18.
Hi,
x == y
x is the left operand.
== is the comparison.
y is the right operand.
"Because the comparison operators are all binary operators that do not modify their left operands"
I don't understand. Could you please use my example to explain?
Thanks,
== is a binary operator, because it has 2 operands.
It doesn't modify its left operand (x). x has the same value after the comparison as it had before.
I am getting an asserting error! what is the peoplem?
What's the error message? Where is the error?
Hi, the problem is in this code line:
the asserting error is: "Debug asserting failed. Expression: Invalid comparator"
Please post your code. Your error sounds like you're using a weird IDE, a misconfigured IDE, or forgot to overload `operator<`
in the quiz, when you overload the < operator with
how does the compiler know how to resolve the if and return lines? Why do you not need to first overload the == and < operators to take strings as parameters?
`std::string`, like many other standard types, already has those operators. See https://en.cppreference.com/w/cpp/string/basic_string#Non-member_functions
I prefer the short form for such comparisons:
And if we have more parameters, the function may be written like this:
Why do we need these overloaded functions? Tested it without these ones and it worked fine.
They're used in the first example of this lesson, but not needed in the quiz.
Is it recommend or preferable to use references in the parameters for these functions, like:
You are not changing the values for the objects, so what's the point in making c1 and c2 references?
We use references for speed. We use `const` because we don't modify them. See lesson 7.3.
The mini quiz was the first one iv'e had any real difficulty with and ultimately gave up and peeked at the answer. Unless I missed something the last time we touched std::sort it was essentially here is sort it sorts arrays when given a beginning and end. I don't remember going over how it actually worked even looking it up I could only really find examples on how to use it which all essentially said the same thing. Give it a starting point, and ending point and a third optional parameter that was how it would compare things.
Looking at the answer now it makes sense but I feel like if something as simple as:
//Note* std::sort by default compares two values and places whichever element it finds the smallest closer to the beginning of an array.
If that little bit of info was there it would have been a lot easier to work backwards how to get std::sort to do what you needed it to do. The second half of the mini quiz was fine though.
I added a link to the lesson where we discuss std::sort and added a little context there. Thanks for the feedback.
Is there any disadvantage in defining the operator as
?
Readability aside, maybe.
It's exactly the same.
Hi, in the quiz where we had to overwrite operator< (Car, Car) for Car class, I found out this worked with std::sort function as well:
You didn't test with enough data
That I did not. Thanks for pointing it out.
The two-way comparison operator expressions have the form:
lhs < rhs (1)
lhs > rhs (2)
lhs <= rhs (3)
lhs >= rhs (4)
lhs == rhs (5)
lhs != rhs (6)
1) Returns true if lhs is less than rhs, false otherwise.
2) Returns true if lhs is greater than rhs, false otherwise.
3) Returns true if lhs is less than or equal to rhs, false otherwise.
4) Returns true if lhs is greater than or equal to rhs, false otherwise.
5) Returns true if lhs is equal to rhs, false otherwise.
6) Returns true if lhs is not equal to rhs, false otherwise.
In all cases, for the built-in operators, lhs and rhs must have either
arithmetic or enumeration type
pointer type
Then there are Arithmetic comparison operators, pointer comparison operators, three-way comparison...
Just a little note for y'all :)
Hi,
What is the correct form of the == operator where instead of:
we have:
Imagine that MyObject has a pointer member variable m_ptr, and we want to test whether this pointer has been initialised. I'm thinking along the lines of:
You can overload typecast operators (Lesson 9.10).
They can be used to write a custom conversion to bool.
Thank you nascardriver :) I was, as is fairly tyoical, barking up entirely the wrong tree
Hello, forgive the constant confirming
Because we are only overloading the operators to manage car classes, does that mean that std::sort can compare strings and sort accordingly? Does it do this by taking the first character's ASCII code and converting it into an int?
@std::sort can sort everything that has an @operator<.
@std::string::operator< compares strings char by char, until the strings differ. char is an integral type, there's no need for a conversion.
Q2. my version of overloaded operator< function.
Hi Jack!
Your code will work in most cases. Not all
c1: hond acivic
c2: honda civic
Correct answer: c1 < c2
Your answer: !(c1 < c2)
On top of that, your code constructs 2 temporary strings, which is slow.
In case anyone is interested. I used the compare function of string and it worked.
Hi Alex!
* Initialize your variables with uniform initialization. You used copy initialization.
* You're comparing the models even if @model_comp is not used.
Hi!
Regarding Quiz(2), in the solution why are the == and != operators overloaded? Aren't the values they compare already resolved to pseudo-fundamental strings when operator < is used? Also wouldn't it be preferable for operator< to be a member function of Car as the left most operand is of type Car?
My solution to Q(2):
> why are the == and != operators overloaded?
They're not used in this quiz, I don't know why Alex added them. It's worth mentioning that C++ doesn't offer default operator== and operator!=. If you want to compare objects of your class, you need to define those operators manually.
> Aren't the values they compare already resolved to pseudo-fundamental strings when operator < is used?
I'm not sure what you mean. @Car::operator< doesn't make use of @Car::operator== or @Car::operator!=. It used @std::string's operators.
> Also wouldn't it be preferable for operator< to be a member function of Car as the left most operand is of type Car?
Lesson 9.4 says not to do so, because with a non-member function it'd be easier to swap the order of the parameters, which doesn't matter in this case.
> My solution
I'm skipping @main, because you copied it.
* Line 4, 5, 9: Initialize your variables with uniform initialization. @m_make and @m_model will be initialized anyway, but if you initialize everything, you won't forget to when you need it.
The overloaded operator== and operator!= were in the original Car program that the quiz question is extending. Even though they're not used, there's no harm in having them there since it's just a copy/paste from above.
Ah k, thanks Alex!
>...C++ doesn't offer default operator== and operator!=. If you want to compare objects of your class, you need to define those operators manually.
If c++ doesnt offer default == or != then how are they used for
where the operator isn't defined or a simpler case
Int in the above example is just to indicate type of x and y, not be correct code.
And what do you define as "default", is the std library and string library "default"? Is it because we arent comparing the objects of defined class (which would require overloading operator) instead the string objects within?
>It used @std::string's operators
Thank you for confirming, thats exactly what I meant, I just wanted to make sure.
>Lesson 9.4 says not to do so, because with a non-member function it'd be easier to swap the order of the parameters, which doesn't matter in this case.
Isnt this case specifically to compare an object of the same type with an object of our defined type as reference? Isn't a case where your defined object is the reference one of the specific times to use a member function?
>* Line 4, 5, 9: Initialize your variables with uniform initialization
Line 4, 5: I thought we werent supposed to initialize a variable both at definition and construction. Is that only if initialized with a value?
Line 9: VS2013 doesn't seem to like uniform initialization and often throws me errors when I try. Do you have any tips or suggestions around these errors?
Thank you for your help. I would have further appreciated if you left your assumption about me copying @main out.
> And what do you define as "default"
Types you write yourself. int, double and other native types have operators. @std::string is a class, it has those operators, whoever wrote the @std::string class that's being used on your system wrote those operators.
> Isnt this case specifically to compare an object of the same type [...]
Sorry, I'm having a hard time trying to understand what you're asking.
> I thought we werent supposed to initialize a variable both at definition and construction
I don't know what you're referring to. Mind sharing the lesson?
Initialize all variables. If possible, to a specific (0) value. @std::string has a default constructor, so just use empty curly brackets.
> VS2013 doesn't seem to like uniform initialization and often throws me errors when I try
If this happens sometimes, but not always, you're doing something wrong (Or there's a compiler bug, which is unlikely). If it never allows uniform initialization, upgrade your compiler.
> I would have further appreciated if you left your assumption about me copying @main
Initialize @v instead of manually pushing all elements. Also, uniform initialization.
I think the right spelling is "nickel" not "nickle".
Typos fixed. Thanks for pointing that out.
i don't understand in second quiz some answers make the program crash such as doing this:
return (c1.m_make < c2.m_make) || (c1.m_model < c2.m_model)
if one or the other is alphabetically out of order sort it , even if it won't do exactly what is required.
does this have to do something with the iterators ?
Hi Michael!
Your @operator< is invalid, because it allows car A to be smaller than car B, but at the same time B is smaller than A.
Depending on the implementation of @std::sort, this could cause infinite loops, exceptions or crashes.