dcsimg

Understanding How to Overload Some of the Unusual Operators in C++

WEBINAR:
On-Demand

Desktop-as-a-Service Designed for Any Cloud ? Nutanix Frame


C++ enables us to overload almost all the operators barring a few. Overloading some of them is highly intuitive whereas some others are a bit tricky. They have a slightly different syntax for overloading, that's all. For example, overloading a subscript operator requires that it must be a member function and single argument. This article gets into explaining the intricacies of overloading a couple of them with simple code examples.

Overview

As we know, when we overload a particular operator, the compiler generates the code that is appropriate, based upon the types of the operand. We must understand that the jobs performed by an overloaded operator can equally and efficiently be performed by explicit function calls. Therefore, the feature of operator overloading is given as an option of convenience to the programmer to make the code look more natural and appropriate under context. For example, the operator plus (+) more succinctly reverberates the idea of concatenating two substring rather than an ordinary function with name such as add() or concat(). In fact, a plus (+) between two substrings is more intuitive and adds to the readability of the code more than an ordinary function.

string greet = "Hello "+" everybody";

Otherwise, we may have to resort to something like this:

string greet = "Hello ";
greet.add("eveybody");

Notice that here we have used another overloaded operator, assignment (=). If greet is a string object, we definitely cannot assign any value with the assignment operator like that. This means that the assignment operator has also been overloaded to perform in the context of string operation. This (overloading of assignment operator) almost gets unnoticed in this trivial instruction. That's good because look how naturally they express the code (the reason for which you may not have noticed). The object behaves like a pure (primitive) datatype. This is the power of operator overloading. It simply adds to the natural flow of the code, enhancing simplicity and readability in a natural manner.

Here is a list of operators that can be overloaded. Some of them are tricky but can be overloaded. Also, there are some that cannot be overloaded at all.

List of operators that may be overloaded in C++
+ - * / % ^
& | ~ ! , =
< > <= >= ++ --
+= -= *= /= %= ^=
&= |= <<= >>= [] ()
-> ->* new new [] delete delete []
List of operators that cannot be overloaded in C++
:: .* . ?:    

Overloading the Comma (,) Operator

The comma (,) operator acts as a separator. It is specifically invoked when a comma is put next to an object of the type the comma is defined for. Note that it will not be called for function argument list. Overloading this operator is not only unusual but also rarely required (better not to).

Now, let us try to overload this operator purely for academic purposes.

#include <iostream>
#include <iomanip>

using namespace std;

static int count=0;

class A {

public:
   const A& operator ,(const A&) const {
      count++;
      cout<<"operator,() invoked "<<count<<" time(s)"<<endl;
      return *this;
   }
};

class B{};

B& operator ,(int, B& b) {
   cout<<"global operator,() is called when comma is placed before
   object"<<endl;
   return b;
}

int main()
{
   A a1,a2,a3,a4;   // This does not invoke the overloaded operator
   a1,a2,a2,a2,a3,a4;   // Operator invoked

   B b1;
   // Operator invoked

   return 0;
}

Note that the class B overload is the comma operator at the global level. This overloading ensures that the operator function invoked even when a comma (,) is placed before the object. As mentioned earlier, a practical requirement of overloading this operator is quite rare.

Overloading the -> Operator

The dereference (->) operator is overloaded when we want to create a smart pointer. A smart pointer means that an object can act like a pointer and perform a pointer-like operation whenever we try to access an object element through them. Therefore, if we want to make an object appear to be a pointer, we may overload this operator. One of the cases where we may need to overload this operator is if we want to wrap a class around a pointer to make it pointer safe.

There are couple of condition to overload this operator: First, it must be a member function; second, it must return a pointer or an object type of the class that it is applied for.

Here is a quick example on how to overload -> operator. It's a bit tricky, though.

#include <iostream>
#include <vector>

using namespace std;
class Item {
   string name;
   double price;
public:
   Item(string name, double price) {this->name = name;
      this->price = price; }
   void show() const { cout << name <<", "<< price
      <<endl; }
};

class ItemList {
   vector<Item*> list;

   public:
     void add(Item* item) {
       list.push_back(item);
     }
     friend class ItemPointer;
};

class ItemPointer {
   ItemList itemList;
   int index;

   public:
      ItemPointer(ItemList& il) {
         itemList = il;
         index = 0;
   }

   bool operator++() {
      bool flag = true;
      if(index >= itemList.list.size())
         flag = false;
      if(itemList.list[++index] == 0)
         flag = false;
      return flag;
   }

   bool operator++(int) {
      return operator++();
   }
   Item* operator->() const {
      Item *it = 0;
      if(!itemList.list[index])
         return it;
      it = itemList.list[index];
         return it;
   }
};

int main() {
   ItemList lst;
   lst.add(new Item("AAA",123.45));
   lst.add(new Item("BBB",235.62));
   lst.add(new Item("CCC",569.21));
   lst.add(new Item("DDD",100.25));
   lst.add(new Item("EEE",65.23));

   ItemPointer iter(lst);
   do {
      iter->show();
   } while(iter++);

   return 0;
}

The Item class defines the object used in this program. Pointers to the Items are stored in the ItemList class. One can add items to the list through the add() method of ItemList. The ItemPointer is declared as a friend class to make the members of ItemList accessible to the ItemPointer. We have overloaded the increment operator (++) so that the ItemPointer can iterate through ItemList using the operator. Observe that ItemPointer behaves like a pointer during the function iter->show() call. We can do more, like a pointer increment, by overloading the increment operator as shown.

There is a key concept, called indirection, behind this (->) operator. Therefore, overloading it paves the way for a clean and efficient way to represent indirection in a program. Perhaps, the implementation of Iterators is its best real-life example.

Operators that Cannot be Overloaded

Some operators are not overloadable. The reason for this restriction is safety and to avoid confusion. For example, the dot operator (.) is used to access the class member. If allowed to be overloaded, the usual access to the class member would be problematic. We may have to use a pointer and the dereference operator (->) to access them. Moreover, overloaded operators are nothing but function calls. As a result, it is impossible to preserve the associativity and precedence rules of the operator according to its built-in semantics. This is the reason four operators are completely restricted from overloading whereas some of them have a tag of caution before using it.

A Word of Caution

Most operators in C++ can be overloaded, but a word of caution, though: It is best not to overload some of the overloadable operators. The reason is: Some operators ensure the order of evaluation with the associated operand. And, recall that overloaded operators are actually function calls. Therefore, the order evaluation naturally cannot be guaranteed with all operators. This particularly true in the case with three operators, namely, logical AND (&&), logical OR (||) and comma (,). Moreover, they also do not preserve short-circuit evaluation properties of the built-in operators. Therefore, it is actually a bad idea to overload them.

Every operator has an associated built-in meaning that we must not destroy or deconstruct by overloading them in any way.

Conclusion

Understand that we only can overload existing operators and cannot invent a new one. The overloaded operators have the same precedence and associativity as we find with its built-in counterparts, regardless of the operand types. We must overload an operator to extend its ability by preserving its inherent semantics.

Although Java as a language does not support operator overloading, but internally it reaps a lot of benefit from it. That asserts its utility. The term production friendly for a language may make it cool and clean but programming for performance is a dirty work, something Java envies while C++ rejoices.

References

  • Lippman, Stanley B., et al. C++ Primer.Addison-Wesley, 2013.
  • Stroustrup, Bjarne. The C++ Programming Language. Addison-Wesley, 2003.


Related Articles

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date