Overloading Operators in C++!
DynamicSquid (4602)

Hey guys! It’s the end of the week. Which means, another tutorial! Last couple weeks, we explored the Monte Carlos method, but this time, let’s change it up a little bit. Instead, we’ll be looking at some C++ operators, and more specifically, overloading them.

Prequisites:

  • OOP
  • 'this' pointer
  • operators

Introduction

What are overloading operators? Well take this example:

class ExampleClass
{
private:
  int value;
};

ExampleClass exampleObject;
exampleObject += 5; // error!

We get an error! Why? Well adding 5 to an object? What does that mean? But how do we add a number to value? Well the attribute is private, so our only choose are methods.

class ExampleClass
{
private:
  int value;

public:
  void add(int addValue)
  {
    value += addValue;
  }
};

ExampleClass exampleObject;
exampleObject.add(5);

Well, that's good an all, but this is C++! There has to be a better way. Overloading operators.

class ExampleClass
{
private:
  int value;

public:
  // method - boring!
  void add(int addValue)
  {
    value += addValue;
  }

  // overloading operator - much better!
  void operator += (int addValue)
  {
    value += addValue;
  }
};

int main()
{
  ExampleClass exampleObject;
  exampleObject.add(5); // methods? pfff... get outta here!
  exampleObject += 5; // overloading operators? much better
}

Don't worry about the syntax for now. Just know that overloading operators change the behavior of normal operators. Now, the compiler doesn't see it as adding 5 to the object, but rather adding 5 to value.

Ready yourself to trash all your getters and setters.


The Basics

Let's start off by taking a look at the previous example.

class ExampleClass
{
private:
  int value;

public:
  // method
  void add(int addValue)
  {
    value += addValue;
  }

  // overloaded operator
  void operator += (int addValue)
  {
    value += addValue;
  }
};

Notice how the method and overloaded operator are very similar. An overloaded operator starts off with it's return type (it's usually void, itself, or bool), then followed by the keyword operator, then the actual operator itself, and a parameter if required. Then inside the overloaded operator function, just do what you would normally do.

Here's another example.

class ExampleClass
{
private:
  int value;

public:
  // prefix increment
  // also works for prefix decrement
  ExampleClass operator ++ ()
  {
    value += addValue;
    return *this;
  }
};

This time we overloaded the prefix increment. This means we can do stuff like this:

ExampleClass exampleObject;

++exampleObject;

Notice how the return type is the object. That's because we use the increment like this:

// returns itself, which doesn't really do anything
// by itself
++exampleObject;

// returns itself, which is needed here
// 'std::cout' cannot print a void type
std::cout << ++exampleObject;

So in the first example, we incremented the object. The object returned itself, but it wasn't being used by anything, so the return type isn't needed there.

But, the second example saw us printing the object, which means we needed a return type there. That's why the increment operator needs a return type.

Let's look at another example.

class ExampleClass
{
private:
  int value;

public:
  bool operator > (const int compareValue)
  {
    return (value > compareValue);
  }
};

int main()
{
  // ignore syntax shortcomings for now

  ExampleClass exampleObject;

  if (exampleObject > 5)
    std::cout << "is bigger";
}

As you can see, this operator overload returns type bool, because it's making a comparison. Also, I added the const there because I'm trying to get into the habit of using const cause... idk, better programming practice?

Now hopefully you guys get the jist of things. Now were going to take a look at what we're going to take a look at :)

Unary Operator - operates on one value

++ (pre- and postfix)
-- (pre- and postfix)
conversion types

Binary Operators - operates on two values

==, !=, <, >, <=, >=
+, -, /, *, %
+=, -=, /=, *=, %=
[] (subscript operator)
() (function operator)


Unary Operators

Increments and Decrements

class squid
{
private:
  int legs;

public:
  squid operator ++ () // prefix
  {
    ++legs;
    return *this;
  }

  squid operator ++ (int) // postfix
  {
    ++legs;
    return *this;
  }
};

Notice how the return type is the object. This is because we use increments like this: cout << ++value;. If it didn't return anything, then cout would be printing a void value.

Conversion Types

class squid
{
private:
  int legs;

public:
  // converts object to `int` (doesn't have to be `int`, could be whatever you want!)
  operator int ()
  {
    return legs;
  }
};

int main()
{
  squid dynamicSquid;

  cout << dynamicSquid; // this works!
  int value = dynamicSquid; // this also works!
}

Notice how the coversion operator just turns the object into an int. It's kinda special since it doesn't have a return type.

Oh, and just a side note, the conversion operator is very powerful. In some cases, that's the only overloaded operator you'll need! Because remeber, it takes an object, and turns it into an 'int'. Very useful.

Binary Operators

Addition Operator

class squid
{
private:
  int legs;

public:
  squid operator + (int addValue) // same for sub, div, mult, and mod
  {
    return legs + addValue;
  }
};

Returns an object for the same reason that the increments return an object.

Assignment Operators

class squid
{
private:
  int legs;

public:
  void operator += (int addValue) // same for sub, add, mult, div, and mod
  {
    legs += addValue;
  }
};

Notice how the return type is void. This is because we don't need to return a value when working with assignment operators.

Comparison Operators

class squid
{
private:
  int legs;

public:
  bool operator == (int compareValue) // same for `!=`, `>`, `<`, `<=`, and `>=`
  {
    return legs == compareValue;
  }
};

The return type is expected.

Subscript Operator

class squid
{
private:
  string colour;

public:
  const char operator [] (int index)
  {
    // this code is incomplete! always make sure you check whether or not the index is withing range!

    return colour[index];
  }
};

int main()
{
  squid dynamicSquid;

  cout << dynamicSquid[2];
}

The return type is expected.

Function Operator

class squid
{
private:
  vector<string> types;

public:
  void operator () (string squidType)
  {
    types.push_back(squidType);
  }
};

int main()
{
  squid dynamicSquid;
  dynamicSquid("small");
}

Again, doesn't return a type because it doesn't need too.


Real Example

Okay, now you know what overloading operators are, let's take a look at a real example.

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class squid
{
private:
  string name;

public:
  vector<string> colours;

  squid(string _name)
    : name(_name) {}

  // conversion type - I'm using this for 'cout'
  operator const char* ()
  {
    string output = "name: " + name;

    // converts string to const char*
    return output.c_str();
  }

  string operator [] (size_t index)
  {
    if (index < colours.size())
      return colours[index];
  }

  // used to insert elements in 'colours' array
  void operator () (string squidType)
  {
    colours.push_back(squidType);
  }

  // used to compare two squids
  bool operator == (squid compareSquid)
  {
    return (name == compareSquid.name);
  }
};

int main()
{
  squid dynamicSquid("Dynamic Squid");
  squid fuzzySquid("Fuzzy Squid");

  // using the comparision operator
  if (dynamicSquid == fuzzySquid)
    cout << "They have the same name!\n";
  
  // using the function operator
  dynamicSquid("pink");
  dynamicSquid("blue");
  dynamicSquid("red");

  cout << "Here are my favourite colours:\n";

  for (size_t a = 0; a < dynamicSquid.colours.size(); ++a)
    cout << dynamicSquid[a] << '\n'; // this is possible due to conversion operator
}

Oh, and what's up with all the squids? Well I'm making my own data type actually called "squid" (coming out soon), so yeah...


And that's it! Most of the operators you'll ever need. If there's one that I missed, please let me know so I can add it in.

And don't forget to upvote :)

You are viewing a single comment. View All
xxpertHacker (780)

If there's one that I missed, please let me know so I can add it in.

All logical and bit-wise operators.

I'm trying to get into the habit of using const.

Nice! I make sure to use immutable data wherever possible.

using namespace std;

And... I just lost all respect for you!

Also, this reminds me of D, which made operator overloading 100x easier, wanna see an example? Oh yeah, I forgot how to code in D... lol :(
D > C++ > C

I honestly think if the tutorial is too long and not interactive enough, people won't care. I'ma make an interactive C++ tutorial, how about that!?

DynamicSquid (4602)

@StudentFires Oh thanks for letting me know, I'll try to add those in soon. Also, I used the standard namespace for readability. I don't use it when I'm actually coding.

Also and interactive tutorial? Yeah that'd be great!

firefish (937)

@xxpertHacker

I'ma

Close: just missed a hyphen. I'm-a