Search

9.6 — Overloading the comparison operators

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!=.

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<=:

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.

Show Solution

2) Add an overloaded operator<< and operator< to the Car class at the top of the lesson so that the following program compiles:

This program should produce the following output:

(Honda, Accord)
(Honda, Civic)
(Toyota, Camry)
(Toyota, Corolla)

Show Solution

9.7 -- Overloading the increment and decrement operators
Index
9.5 -- Overloading unary operators +, -, and !

74 comments to 9.6 — Overloading the comparison operators

  • Hi,

    I attempted the Car program thus:

    but it returned with the following error:

    Severity    Code    Description    Project    File    Line    Suppression State
    Error (active)    E1776    function "std::basic_ostream<_Elem, _Traits>::basic_ostream(const std::basic_ostream<_Elem, _Traits> &) [with _Elem=char, _Traits=std::char_traits<char>]" (declared at line 83 of "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.15.26726\include\ostream") cannot be referenced -- it is a deleted function    Car    C:\Users\Nigel\Documents\Visual Studio 2017\Projects\Car\Car\Car.cpp    63

  • Silviu

    Hello,

    why here it's put straight (c1==c2) and not as c1.m_make==c2_m_make && c1.m_model==c2.m_model ?

    I tried it, doesn't work.

    I know that line always returns true to work.
    Thank you.

    • Hi Silviu!

      Because we already wrote

      in @operator== and can re-use that function instead of having duplicate code.

      • Silviu

        Hello again 🙂
        In the second quiz, obviously i didn't succeed to finish the problem, but i have some questions.

        why the friend function bool operator< doesn't need a function definition ?

        why it uses operator== and operator!= if it uses operator< to compare them and sort them ?

        why i need a function to compare ( like friend bool operator< ) the objects if we use std::sort ? i mean sort knows how to sort strings. I'm thinking it has the type bool and it sorts by the condition of that function, because bool type returns just 0 or 1 and std::sort wouldn't be effective without that function.
        Thank you.

        • > why the friend function bool operator< doesn't need a function definition ?
          It needs a definition and it has a definition. Line 27-33 of the solution

          > why it uses operator== and operator!= if it uses operator< to compare them and sort them ?
          @operator== and @operator!= are in the quiz so you learn how to use them. They aren't required for sorting.

          > why i need a function to compare ( like friend bool operator< ) the objects if we use std::sort ?
          How is @std::sort supposed to know how to order objects without knowing if one object is bigger/smaller than another one?

          > sort knows how to sort strings
          Strings have @operator<

          • Silviu

            sorry about the function definition. I had my code something like this:

            i had the function inside the class and it still worked, that's why i still wondered how...
            ok about operator== and operator!= , thank you.
            I thought that sort knows which is big or small ( v.begin(), v.end() ) , like when it's used in the array and want to sort from 1-5. I mean compares all the numbers and here compares by the first letter in the word.

            • > i had the function inside the class and it still worked
              Functions can be defined inside of classes

              > I thought that sort knows which is big or small
              But how is it supposed to do so when you don't define what's big and small? If you define a class "Car" without a relational operator, how is @std::sort supposed to know if one car should be before or after another car? The parameters to @std::sort only specify the range in which it should sort, usually the entire array (begin to end).

              > compares by the first letter in the word
              "car"
              "cat"
              You can't compare them with only the first letter. @std::string::operator< compares until it finds a letter that is different in both words.

              • Silviu

                ok, thank you .
                >Yes you are right in the first question, reading them only once and being so many new things it's hard to dig in.
                >Now i understand, i was a little confused, just to many overloading lessons and my brain got overloaded^^....
                >Being confused i forgot how they really compare.
                Thank you again for the help.

  • Quoxa

    Hi Alex,
    making a new comment because I am not sure whether to reply in Olivier's or radu f's comment thread.

    I am not sure what your original code was, but if my understanding serves me right, radu f's

    should work as well as (if not, bettter than) Olivier's

    because of how string::compare and short circuiting work.

    In fact, Olivier compared the strings twice, the first time in

    and the second time in

    • Hi Quoxa!

      I didn't read this lesson, so I don't know what restrictions were set to @m_make and @m_model. Assuming there are no restrictions, the two snippets are not equivalent.

      Take c1.m_make > c2.m_make:
      radu's code will return

      whereas olivier's code returns

  • DecSco

    Quiz solution: My first thought was to just invert the order of arguments:

    This saves a couple of instructions, I guess, and should be just as clear. May be provided as an alternative solution.

  • FuzzyWuzzy

    Alex,

    Thanks so much for a wonderful C++ tutorial.

    Any reason not to use std::string concatenation for operator< in question 2?

    • Alex

      It's less efficient, since you're constructing two new temporary objects just so you can compare them.

      The reference answer uses a few more lines, but doesn't create any temporaries, so it should run much faster.

      • FuzzyWuzzy

        Ah, that makes sense, thanks very much Alex! Also, thanks again for your amazing set of tutorials. They far surpass all of the books to which I have devoted so much time.

  • Olivier

    Hello Alex, your lessons are wonderful, thanks a lot! I came up with the code below for overloading operator<. I think that the result is identical to yours, but mine is slightly more concise. And with the added comments maybe it's easier to understand what we're doing here, sorting alphabetically based on make and model. Could you please tell me what you think about it?

  • radu f

    Hello,

    is it a specific reason for not using the overloaded operator < in the second problem like this:

    ?

    • nascardriver

      Hi Radu!

      I prefer your solution. I don't know why Alex used so many checks,

      has the simplicity of your code with the clarity of Alex' code.

      • Alex

        My code is longer because Because your code and mine are not the same.

        Consider two cars:
        c1: m_make = b, m_model = c
        c2: m_make = a, m_model = d

        At the second if statement:
        In my code, c1.m_make > c2.m_make is true, so my code returns false at this point.
        In your code, c1.m_model < c2.m_model is true, so your code returns true at this point.

      • Donlod

        Both of those solutions crash the program for me in some .dll file.

  • Scott

    Thanks for the useful tutorial.  However, I would raise a caveat on your following assertion:
        It doesn’t really make sense to overload operator> or operator< for the Car class. ...
        Greater than or less than isn’t a concept we normally apply to cars, so it’s better not
        to include those operators in the Car class

    The problem with NOT defining operator< is that if one creates any std container with Car contents, the ordering of elements (for an ordered container like set or map) and even equality (2 elements are equivalent if their comparison returns false reflexively) is left to the mercy of default operator<. For Car we might be OK with the default (lexicographic comparison of both string members), but in general one can get some unanticipated behavior with default comparison.

  • Benjamin

    Hi,

    instead of inverting the result of the complementary operator, it is also possible to invert the order of the operands using the complementary operator. So instead of

    You can do

    Probably a bit trivial, but it is saving an operation 🙂

    • nascardriver

      Hi Benjamin!
      You've got a thinking error in your logic.
      Example:

      • Benjamin

        True, my bad. Well, then do

        instead. This should do the trick.

  • Omri

    In the following taken from above, what do you mean in the parenthesized text:
    "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."

  • Omri

    Hello Alex,
    Regarding: "Because the comparison operators are all binary operators that do not modify their left operands, we will make our overloaded comparison operators friend functions".
    You answered Chandra below:
    "You can define a comparison operator as a member function so long as you can modify the class of the left operand"...
    Does this mean you prefer the "friend function form" so that you can "const" both arguments in the function declaration&definition?

    • Alex

      I prefer the friend function form for two reasons:
      1) It's more symmetric, where both operands are treated as parameters rather than having one operand be implicit and one be explicit.
      2) It's more flexible, in that it can be used even when you can't modify the class of the operand (e.g. because that class is part of the standard library).

  • Hi. I've come across this code.

    You are supposed to enter the ID and the length of a number of Boxes. Then you use the "sort" function to sort the array of classes according to the length of boxes in ascending order. And print the ID and length of each element of this sorted array.

    The overloading of the operator < doesn't seem to fit in any of the categories (friend/normal/member). It is only defined inside the class, not in a global scope.
    How does the programm know where to look for the overloading of the operator?
    This way seems easier since you only write your code once for the overloading and you don't bother with friend keywords, access functions etc..
    I am confused !

    • Alex

      In this case, operator< is a member of the class, so it fits as a member. The compiler is smart enough to check both member and non-member functions to see if it can find a matching function. In this case, it has been defined as a member.

      Personally, I don't like defining binary operators as members, as it's harder for me to intuit that the left hand object is the implicit object, and is being compared against the right hand parameter. With friend/normal functions, there's more symmetry, as both objects are explicit parameters to the function.

  • Sol

    Would you please teach at my school please? 😉

    Thanks for everything, I think your C++ tutorials are some of the best if not the best on the web.

  • You wrote:
       bool operator!= (Car &c1, Car &c2)

    Surely two "const" are missing there, before each Car &?

  • Matt

    Thanks for the response, Alex.

    Actually, I understood about the precision problem when comparing floats, and that it should be avoided. What I was refering to is the fact that the comment(regarding floats) was referencing the code above which was only comparing strings. I thought that either I had missed something, or that maybe you had switched out float code to a string code example at some point, but forgot to update your article to reflect that change.

  • Matt

    Alex,

    Below your first code example, I don't understand how this comment relates to the code:
    "(and of course, we really shouldn’t be comparing floats using operator == or != anyway)"

  • Mukki

    Hi, Alex! Thanks for the tutorials! This might be very silly but I have a doubt in the Car class where you compare c1==c2. It is calling operator==. How does the control flow here?

    • Alex

      You're talking about in operator!= ?

      Basically, c1 is compared to c2 using operator==. Then the boolean result is flipped via operator!. Then that value is returned to the caller.

  • Nyap

    shouldn't quiz time be on a new line

  • Sagar Sanghavi

    Hi Alex,

    In the quiz question, did you intend the answer to be this:

    instead of:

    Because only then we are ensuring that we re-use the above function, i.e. "operator>=" correct?

  • Chandra

    Thanks Alex. Such a nice website/tutorial.
    I had a question.
    I was wondering if comparison operators MUST be defined as friend functions, if both the operands are of same type. a member function with right operand as the required class type should be fine right!

    i agree, if the left operand is of different type, then we might have to go for a Friend function.

    And, your text: "Because the comparison operators are all binary operators that do not modify their operands, we will make our overloaded comparison operators friend functions." confuses me.

    is it that, "operators do not modify operands" the criteria to define it as a friend function? Can you please explain?

    • Alex

      You can define a comparison operator as a member function so long as you can modify the class of the left operand.

      "operators do not modify operands" is not a criteria to define an operator as a friend function, but it's generally easier to understand what the operator is doing when written in "friend mode" rather than "member mode" since the left operand doesn't become the implicit *this object. The friend version provides better symmetry since both operands are function parameters.

      Similarly, writing operators in "member mode" is better when the operator does modify the class (e.g. operator+=) because then it's easier to tell what is getting modified (*this) vs. what's a parameter.

  • Valentino

    Why does (Cents > int) use my overloaded operator (Cents > Cents)?
    Thanks

    • Alex

      Because one of your operands is an object of type Cents, C++ will first look to see if there is an operator where both operands match. In this case, there isn't. Next, the compiler will try to see if it can promote or convert types to make the operands match. In this case, because you have a Cents constructor that takes an int, the compiler will use this constructor to create a Cents object from the int.

      It's essentially implicitly converting the if statement to this:

      • Valentino

        Thank you for your response. Is it possible to tell C++ that it should not have to do that so I will get an error on compile?

        • Alex

          Yes, put the explicit keyword before your constructor name. This will prevent C++ from using it for type conversions.

  • nick

    Hi Alex;
    Should not the ! operator overloaded

    • Alex

      It could be implemented that way, and if operator > is complicated, I'd recommend it, to reduce the amount of redundant code.

      However, when the implementation of the comparison operators is trivial, it's easier (and more understandable) to just implement them individually.

  • j_nick

    operator> and operator<= are logical opposites, so one could be defined in terms of the other. operator< and operator>= are also logical opposites
    const ?

    • N.ab

      Hi Alex : is correct ?...

      • Alex

        Yes. If two operators are logical opposites, one can be defined as the negation of the other.

        If the implementation of the operator is trivial, like the examples in the lesson, this probably isn't worth doing (it's easier to just implement it).

        If the implementation of the operator is more complicated, then this can be worth doing as a way to reduce redundant code.

  • j_nick

    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

  • N.Abb

    Why we have to overload relational operators in pairs ?
    like if I overload == I must overload !=
    Thanks

    • Alex

      You don't have to. However, since one can be defined in terms of the other, it's almost trivial to write the second after you have the first.

    • Darren

      C++ is sweet with syntax sugar; it's not needed but it is nice to have.

      A function or operator that has a "positive" effect on a class should in best practice, and where possible, come with a "negative" twin (note this is not a requirement). For example,
      consider a class that represents bank accounts. If there is a function/operator that allows users to deposit cash, then there should be a opposite function/operator that allows them to withdraw their cash (unless the bank is particularly devious). Or if you have an operator to sum two vectors, you'd expect to have an operator to find the difference between two vectors. There are exceptions. For example, you can multiply two matrices, but matrix division is undefined (instead this would be matrix factorisation).

  • "operator> and operator<= are logical opposites, so one could be defined in terms of the other. operator< and operator>= are also logical opposites"

    shouldn't it be:

    "operator> and operator< are logical opposites, so one could be defined in terms of the other. operator<= and operator>= are also logical opposites"

    (last paragraph)

  • Rahul

    Why can't I overload operator '~=' same way I overload '=='? I want it to have same definition as '!='.

    It gives error "declaration of ‘operator~’ as non-function". Is it an error with my syntax?

    I read somewhere that C++ overloads operators whose definitions are already known? Is that true? For example can't overload definition of '#' operator. Or define my own operator.

    my code:

    • Alex

      In C++, you can only overload operators that already exist in C++. ~= isn't an operator that naturally exists in C++, so you can't overload it.

  • j.howard

    Great tutorial as usual. Just as an idea the < and > operators could be used to compare the magnitude of the vector from the origin to the point in the Point class.

  • Ameerah

    thanks Alex very mutch i love you totorile

    simple &easy

Leave a Comment

Put all code inside code tags: [code]your code here[/code]