Operator Overloading

I begin with a walkthrough on how to overload an operator step by step. A very simple class has been taken as an example. It is followed by examples on overloading the inserters and extractors, the comma operator, then the subscript operator. The article ends with an example of overloading the function call operator and the C++ functors.

Last Reviewed and Updated on February 7, 2020
Posted by Parveen(Hoven),
Aptitude Trainer and Software Developer

My C/C++ Videos on Youtube

Here is the complete playlist for video lectures and tutorials for the absolute beginners. The language has been kept simple so that anybody can easily understand them. I have avoided complex jargon in these videos.

Operator overloading is giving custom meanings to the various operator symbols. The whole point is to make your code more readable and intuitive to understand. Take an example. A compiler knows how to add two numbers but what about adding two complex numbers represented by objects of a class used to model complex numbers? The compiler has no idea about what to do when it sees two objects surrounding the plus sign. As far as a mathematician is concerned, he knows what the addition of two complex numbers means, but the compiler doesn't. Operator overloading is about writing suitable functions that the compiler can execute when it sees non-numbers surrounding an operator.

The operator Keyword

The operator keyword is used to declare a function for an operator symbol. For example if we have to overload the plus operator, then the function would look like operator+. Similarly, if we have to overload the compound assignment then the function would look like operator+=.

Overloading the Addition Operator

I will begin in a very step by step manner. So, let us suppose we have to define rules for adding two objects of the following class.

class CMyClass
{

    int i;

public:
    CMyClass(int x) : i(x)
    {

    }

};


We should decide three things first -

  1. Arguments: First we should decide on the arguments that this operator is going to operate on. The addition operator is a binary operator, so it will operate on two objects of the class CMyClass. This means that our function will take two parameters each of type CMyClass. Now, another question to ask is: whether the arguments would be modified? The answer is obviously no. So the arguments can be passed by const reference.
  2. Return: The second thing we need to decide on is the return type. Like, what would be the result of addition. In our case the sum of two objects of the class CMyClass should also be of the type CMyClass so that we could chain two or more plus signs. The result will have to be a third object. So the return must be by value.
  3. Process: The most important thing is that we should be able to define what will be the purpose of adding two objects. What is the business logic for adding two objects? In our case we can say that by adding two objects of the CMyClass we shall be creating a new object whose i is equal to the sum of i of the two operands.

Now we can add a global function for adding two objects like this.

CMyClass operator+(const CMyClass& cmc1, const CMyClass& cmc2)
{

    int iSum = cmc1.i + cmc2.i;

    CMyClass obj(iSum);

    return obj;

}


We can notice a few points here. The name of the function is operator+. It takes two operands as two arguments. It performs an addition of the data members of the two operands, then creates a new object and returns it by value. The complete code is shown below.

class CMyClass
{

    int i;

public:
    CMyClass(int x) : i(x)
    {

    }

public:
    void print()
    {

        cout << "i = " << i << endl;

    }

    friend CMyClass operator+(const CMyClass&, const CMyClass&);

};

CMyClass operator+(const CMyClass& cmc1, const CMyClass& cmc2)
{

    int iSum = cmc1.i + cmc2.i;

    CMyClass obj(iSum);

    return obj;

}

int main()
{

    CMyClass obj1(8);

    CMyClass obj2(-8);

    CMyClass obj3 = obj1 + obj2;

    obj3.print();

    return 0;

}


The global function has been declared a friend function because i is private member of the CMyClass and it has to be accessed within the function.

Overloading an operator by using a global function is not the only way. It can be overloaded by making it a member function. In that case the first argument is implicitly the object on the left hand side. This is the complete code in this case.

class CMyClass
{

    int i;

public:
    CMyClass(int x) : i(x)
    {

    }

public:
    void print()
    {

        cout << "i = " << i << endl;

    }

    // overloaded operator is a class member
    CMyClass operator+(const CMyClass& cmc2)
    {

        int iSum = this->i + cmc2.i;

        CMyClass obj(iSum);

        return obj;

    }

};

int main()
{

    CMyClass obj1(8);

    CMyClass obj2(-8);

    CMyClass obj3 = obj1 + obj2;

    obj3.print();

    return 0;

}


The operator function now takes only one argument, the other being the left hand side object.

For reasons already explained above, we can chain the addition of three objects.

int main()
{

    CMyClass obj1(8);

    CMyClass obj2(-8);

    CMyClass obj3(100);

    CMyClass obj4 = obj1 + obj2 + obj3;

    obj3.print();

    return 0;

}


Do Anything!

Most operators can be defined, and can be defined in any way, because you have full freedom to write code in any way you choose. The insertion and extraction operators are traditionally used for input and output, but they too can be overloaded and given any intuitive meaning. To me the arrows pointing towards the right can be used to create zoom out effect, and the arrows towards the left can be used for zoom in. Below is a class that contains the width and height of a drawing. The extraction and insertion operators have been overloaded to give the effect of zoom in and zoom out.

class CDrawing
{

    int width;

    int height;

public:
    CDrawing(int w, int h)
    {

        this->width = w;

        this->height = h;

    }

public:
    // zoom in - enlarge
    CDrawing& operator<<(int factor)
    {

        if(factor > 0)
        {

            this->height *= factor;

            this->width *= factor;

        }

        return *this;

    }

    // zoom out - make smaller
    CDrawing& operator>>(int factor)
    {

        if(factor > 1)
        {

            this->height /= factor;

            this->width /= factor;

        }

        return *this;

    }

public:
    void print()
    {

        cout << "Width = " << width << endl;

        cout << "Width = " << height << endl;

    }

};

int main()
{

    CDrawing dg(10, 10);

    // zoom in
    dg << 3;

    dg.print();

    // chaining possible
    dg >> 5 >> 2;

    // this also!
    dg << 10 >> 5;

    dg.print();

    return 0;

}


The return from both my overloads is a reference to the class object itself. This helps us to chain the operators. The chaining can be done for both of them in the same statement. It's because of my custom overloading.

An operator like a comma operator can also be overloaded. There might not be many use scenarios for overloading a comma operator, but the C++ language does make this option available to us. Following is an example where I have overloaded the comma operator just for the sake of it. Two objects of the class CItem will display as a pair put inside round brackets.

class CItem
{

    int i;

public:
    CItem(int x) : i(x)
    {

    }

public:
    void operator,(CItem ci)
    {

        cout << "(" ;

        cout << this->i ;

        cout << ", " ;

        cout << ci.i ;

        cout << ")";

    }

};

int main()
{

    CItem ci(8), ci2(78);

    // comma operator comes into play here
    (ci, ci2);

    return 0;

}


The subscript operator can also be overloaded to provided access to elements of a class that models a collection. But this is not the only use. Below I give an example where the subscript operator has been overloaded to allow access to its data members by name. The operator takes a string as an argument. Then a ladder of if and else is used to return the value of the matching variable.

class CData
{

    int i;

    int j;

    int k;

public:
    CData(int x, int y, int z) : i(x), j (y), k(z)
    {

    }

public:
    int& operator[](string s)
    {

        if(s == "i")
        {

            return i;

        }

        else if(s == "j")
        {

            return j;

        }

        else if(s == "k")
        {

            return k;

        }

        else
        {

            cout << "Item not found" << endl;

            static int ERR = -1;

            return ERR;

        }

    }

};

int main()
{

    CData cd(6, 8, 3);

    cout << cd["i"] << endl;

    cd["k"] = 34;

    cout << cd["k"] << endl;

    return 0;

}


The overloading of the subscript operator can be used to create a smart array that never moves past its borders. In the code below, when the user enters a number greater than 9, the array ceils to 9. Similarly it floors to zero for negative indices.

class CSmartArray
{

    int i[10];

public:
    int& operator[](int x)
    {

        if(x < 0)
        {

            cout << "WARNING... BOF" << endl;

            return i[0];

        }

        else if(x > 9)
        {

            cout << "WARNING... EOF" << endl;

            return i[9];

        }

        else
        {

            return i[x];

        }

    }

};

int main()
{

    CSmartArray arr;

    arr[0] = 88;

    arr[1] = 99;

    arr[100] = 2;

    cout << arr[100] << endl;

    return 0;

}


Operators that can't be Overloaded

All operators except the following can be overloaded.

  1. . - The dot operator.
  2. .* - Pointer to member selection.
  3. :: - Scope resolution.
  4. ?: - Conditional ternary operator.
  5. # - Preprocessor convert to string.
  6. ## - Preprocessor concatenate.

Functors

A functor is a class object that can behave like a function. In the following example we have a class CAbc that has an overloaded function call operator. The overload takes two arguments, and returns their sum. Inside main we have created two objects of this class, and given them readable names like sum and add. Then these objects behave like functions with names "sum" and "add". Notice that these are mere objects. There are no functions by these names. Detailed discussion about functors is beyond the scope of this article. But this code is a good example to see the concept.

class CAbc
{

public:
    int operator()(int x, int y)
    {

        return x + y;

    }

};

int main()
{

    CAbc sum, add;

    cout << sum(8, 9) << endl;

    cout << add(8, 9) << endl;

    return 0;

}



Creative Commons License
This Blog Post/Article "Operator Overloading" by Parveen (Hoven) is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Updated on 2020-02-07. Published on: 2015-12-23