Understanding Operator Overloading in C++

ยท

5 min read

In this article, you will start to understand operator overloading in C++.

What is it?

Operator Overloading can give a special behavior (or meaning) to an operator when it is applied to an instance of a class or a user-defined data type.

The problem

For example, what happens if we have two instances of a class and we try to add one with another? The compiler in that case doesn't know what to do, then will throw an error.


class Color {
public:
    Color(name) 
        : name(name) {}

    std::string name;
};

int main() {
    Color green("Green");
    Color red("Red");

    Color yellow = green + red;
    // error: no match for 'operator+' (operand types are 'Color' and 'Color')
    return 0;
}

Have you seen it? We are not able to add the colors because the compiler doesn't know the operator's behavior.


Different operators can be overloaded, here are a few:

  • Addition: +

  • Subtraction: -

  • Multiplication: *

  • Division: /

  • Equality: ==

  • Addition assignment: +=

  • Logical AND: &&

How does it work?

Well, let me give you an example.

Do you remember Cell from Dragonball Z? With his Absorption technique, he can absorb an opponent and his strength.

cell absorbs gif

We can try to replicate the Cell Absorption Technique in code by overloading the plus (+) operator.

enum Kind {
    Human,
    Android
};

class Character {
public: 

    Character(string name, Kind kind, float strength)
        : name(name), kind(kind), strength(strength) {}

    string name;
    Kind kind;
    float strength;
    bool isAlive{true};
};

int main() {
    return 0;
}

In the code above we have an Enum, which helps to identify the Character Kind,
and a Character Class with its constructor and some member variables.

Now we can write the operator function inside the class

Character operator+ (Character& c) {
    if(kind == Kind::Android) {
        c.isAlive = false;
        // Here we simply return a Character, but with the strength addition
        return Character(name, kind, strength + c.strength);
    } else {
        // If the charactor is not an Android, just return it.
        return Character(name, kind, strength);
    }
}

๐Ÿง
As we can observe, the syntax is the following
return_type operator symbol (arguments) {
    // ....
}

Let's initialize two characters

int main() {
    Character Cell("Cell", Kind::Android, 9000.0);
    // Name: Cell
    // Kind: Android
    // Strength: 9000.0
    // isAlive: true

    Character Krillin("Krillin", Kind::Human, 750.0);
    // Name: Krillin
    // Kind: Human
    // Strength: 750.0
    // isAlive: true

    return 0;
}

Now Cell can use his technique on Krillin!

int main() {
    Character Cell("Cell", Kind::Android, 9000.0);
    Character Krillin("Krillin", Kind::Human, 750.0);

    // Absorption!
    Cell = Cell + Krillin;

    Cell.name; // Cell
    Cell.kind; // Android
    Cell.strength; // 9750.0
    Cell.isAlive; // true

    Krillin.name; // Krillin
    Krillin.kind; // Human
    Krillin.strength; // 750.0
    Krillin.isAlive; // false

    return 0;
}

Pretty straightforward right?
As you can see, the Cell's strength has increased to 9750.0. This was achieved by "absorbing" Krillin's strength with the help of the overloaded plus operator!

Let's put it all together ๐Ÿ‘‡

#include <iostream>
#include <string>
using std::string;

enum Kind {
    Human,
    Android
};

class Character {
public: 

    Character(string name, Kind kind, float strength)
        : name(name), kind(kind), strength(strength) {}

    Character operator+ (Character& c) {
        if(kind == Kind::Android) {
            c.isAlive = false;
            // Here we simply return a Character, but with the strength addition
            return Character(name, kind, strength + c.strength);
        } else {
            // If the charactor is not an Android, just return it.
            return Character(name, kind, strength);
        }
    }

    string name;
    Kind kind;
    float strength;
    bool isAlive{true};
};

int main() {
    Character Cell("Cell", Kind::Android, 9000.0);
    Character Krillin("Krillin", Kind::Human, 750.0);

    // Absorption!
    Cell = Cell + Krillin;

    Cell.name; // Cell
    Cell.kind; // Android
    Cell.strength; // 9750.0
    Cell.isAlive; // true

    Krillin.name; // Krillin
    Krillin.kind; // Human
    Krillin.strength; // 750.0
    Krillin.isAlive; // false

    return 0;
}

Implementing a Vector2

We can write another more practical example by making a simple implementation of a Vector2, let's do it!

(For those who don't know, a Vector2 defines a position in space)

Here we have a class called Vector2

class Vector2 {
public:
    Vector2()
        : x(0.0), y(0.0) {}

    Vector2(int x, int y)
        : x(x), y(y) {}

    // Vector2 + Vector2
    Vector2 operator+ (Vector2 const& _vec2) {
        return Vec2(x + _vec2.x, y + _vec2.y);
    }

    // Vector2 += Vector2
    Vector2& operator+= (Vector2 const& _vec2) {
        x += _vec2.x;
        y += _vec2.y;

        // Return the left-hand Vector2
        return *this;
    }

    // Vector2 == Vector2
    bool operator== (Vector2 const& _vec2) {
        if (x == _vec2.x && y == _vec2.y)
            return true;
        else
            return false;
    }

    float x, y;
};

Now that we have written the behavior of those operators let's see what happens in the code. Assume that we are making a videogame and there are two Actor:

  • AHero

  • AMonster

Those Actors have different positions.

// Initialize actors
Vector2D AHero(3.0, 1.0);
Vector2D AMonster(5.0, -3.0);

Using the + operator

AHero = AHero + Vector2(1.0, 1.0);
// AHero now has a new position

std::cout << AHero.x << " : " << AHero.y << std::endl;
// 4.0 : 5.0

Using the += operator

AHero += Vector2(3.0, -5.0);
// AHero now has a new position

std::cout << AHero.x << " : " << AHero.y << std::endl;
// 6.0 : -4.0

Using the == operator

// Initialize actors
Vector2D AHero(3.0, 1.0);
Vector2D AMonster(5.0, -3.0);

// The hero moves
AHero += Vector2(1.0, 1.0); // 4.0, 2.0

// The monster moves to the hero position
AMonster = AMonster + Vector2(-1.0, 5.0);

// Now we check if the two actor are in the same position.
// In this case, the hero dies.
if (AHero == AMonster) {
    // The monster kills the hero.
    // ...    
} else {
    // The hero will live.
    // ...
}

And that's it.


If you want to see how to overload other operators and have a better understanding of this concept, I left for you some interesting resources.

Introduction to operator overloading:
https://www.learncpp.com/cpp-tutorial/introduction-to-operator-overloading/

TheCherno:
https://www.youtube.com/watch?v=mS9755gF66w

CodeBeauty:
https://youtu.be/BnMnozsSPmw?si=ZORSRExLocjHNJPi

C++ Logical (&&, ||, !) Operator Overloading:
https://www.geeksforgeeks.org/cpp-logical-operator-overloading/

ย