4.2. Operator overloads

Very few languages allow overloading basic operators. In this section, we won’t discuss all possible overloading, but we are introducing some of the more common overloads that generally are implemented as ordinary ‘free’ functions.

In C++, operators are overloaded in the form of functions with special names. For example, a+b and operator+(a,b) both call the same function.

Most C++ operators can be overloaded. You cannot change the meaning of operators for built-in types in C++. Operators can only be overloaded when at least 1 operand is a user-defined type. Other rules of overloads still apply: overloads for a specific function signature can only be used once.

Some of the most commonly overloaded operators are << and the relational operators: ==, !=, <, <=, >, and >=.

It doesn’t always make sense to overload all of the relational operators. For example, a complex number does not have a natural order, so you may only want to overload == and != for a complex number.

If you overload == you should always overload !=. If you overload == and <, then you should overload all 6 relational operators.

4.2.1. Relational operators

Standard algorithms such as std::sort and containers such as set expect operator < to be defined, by default, for the user-provided types, and expect it to implement strict std::weak_ordering. Strict weak ordering defines members of a set as comparable to each other. The general signature for these non-member functions is:

// In this example, T is a placeholder for your type.
// Note that this is not a function template.
inline bool operator<(const T& lhs, const T& rhs)
{
   // compare the data in left-hand side and right-hand side objects
   // for less than
}

inline bool operator==(const T& lhs, const T& rhs)
{
   // compare the data in left-hand side and right-hand side objects
   // for equality
}

An idiomatic way to implement strict weak ordering for a structure is to use lexicographical comparison provided by std::tie:

struct Record
{
    std::string name;
    unsigned int floor;
    double weight;
};

inline bool operator<(const Record& lhs, const Record& rhs)
{
   // parameters passed to each tie must be in the same order
   // or this will always return false
   return std::tie(lhs.name, lhs.floor, lhs.weight)
        < std::tie(rhs.name, rhs.floor, rhs.weight);
}

If some of the data required for the comparison is private and has no function to access the data members, then you may need to make your relational operators friends.

Once you have defined operator< and operator==, there is no need to rewrite the comparison logic again. It is much better to implement the remaining comparison functions in terms of < and ==.

// note the operands swapped inside the function body
inline bool operator> (const T& lhs, const T& rhs){ return   rhs < lhs; }

inline bool operator<=(const T& lhs, const T& rhs){ return !(lhs > rhs); }
inline bool operator>=(const T& lhs, const T& rhs){ return !(lhs < rhs); }

inline bool operator!=(const T& lhs, const T& rhs){ return !(lhs == rhs); }

Note

It is a common programming anti-pattern to reimplement all the logic for each relational overload.

This is a common source of error and can lead to bugs that are very difficult to track down.

4.2.2. Insertion and extraction overloads

The bitshift operators << and >>, although still used in hardware interfacing for the bit-manipulation functions they inherit from C, have become more prevalent as formatted stream operators in C++.

The overloads of operator >> and operator << that take a std::istream reference or std::ostream reference as the left hand argument are known as insertion and extraction operators.

The canonical forms are:

std::ostream& operator<<(std::ostream& os, const T& rhs)
{
  // write rhs to stream
  return os;
}

std::istream& operator>>(std::istream& is, T& rhs)
{
  // read rhs from stream
  if( /* could not construct T from stream */ ) {
    is.setstate(std::ios::failbit);
  }
  return is;
}

When we use classes such as cout, the << operator looks at the type on the right-hand side to determine which overload to call.

These two lines of code call the exact same function:

std::cout << "howdy!";

operator<< (std::cout, "howdy!");

More to Explore

You have attempted of activities on this page