Operator Overloading – Introduction to Boost.Operators, Part 1

In my first two posts about operator overloading I have written about the basics and common practice. This post shows some lessons from the common practice post on a concrete example and then introduces to Boost.Operators, a library that conveniently reduces the boilerplate involved when overloading multiple operators for a class.

Operators Travel in Packs

If we look at the list of operators, we see that there are about 50 of them, and many of them can be overloaded in different ways. Even if we restrict ourselves to a few operations that make sense for a given class, then one of those operations often brings two or more operators.

Example: a Class For Rational Numbers

Let us consider a common example for a mathematical class, `class Rational`. The usual operations for it would be the four basic arithmetic operations, and in addition the change of sign, comparison for equality and ordering relation. The declaration of the class with its operators would be easy to write:

class Rational {
public:
  Rational operator-() const;
};

Rational operator+(Rational const& lhs, Rational const& rhs);
Rational operator-(Rational const& lhs, Rational const& rhs);
Rational operator*(Rational const& lhs, Rational const& rhs);
Rational operator/(Rational const& lhs, Rational const& rhs);
bool operator==(Rational const& lhs, Rational const& rhs);
bool operator<(Rational const& lhs, Rational const& rhs);

That’s it. Seven operations make seven operators to overload. But we’re just getting started. In the “Basics” post I had written that operators should behave as expected. That does not only concern the operators themselves, but also the expectation that e.g. the presence of an `operator+` implies the presence of an `operator+=`, an `operator<` means there should be an `operator>` and so on.

Most of those operators are part of operator families, and when we overload one of them, we should overload the whole family. So the extended declaration looks like this:

class Rational {
public:
  Rational operator-() const;
  Rational operator+() const; //new

  Rational& operator+=(Rational const& rhs); //new
  Rational& operator-=(Rational const& rhs); //new
  Rational& operator*=(Rational const& rhs); //new
  Rational& operator/=(Rational const& rhs); //new
};

Rational operator+(Rational const& lhs, Rational const& rhs);
Rational operator-(Rational const& lhs, Rational const& rhs);
Rational operator*(Rational const& lhs, Rational const& rhs);
Rational operator/(Rational const& lhs, Rational const& rhs);
bool operator==(Rational const& lhs, Rational const& rhs);
bool operator!=(Rational const& lhs, Rational const& rhs); //new
bool operator<(Rational const& lhs, Rational const& rhs);
bool operator>(Rational const& lhs, Rational const& rhs); //new
bool operator<=(Rational const& lhs, Rational const& rhs); //new
bool operator>=(Rational const& lhs, Rational const& rhs); //new

Now we have 16 operators. That seems a bit more work than we thought in the first place.

It’s All Routine

When we roll up out sleeves and start to implement all those operators, we will notice that much of the work is repeated boilerplate. In the “Common Practice” post I have shown how many operators can be implemented in terms of others. Only a few need a “real” implementation, the rest will be the same for any class that is similar to `class Rational`:

class Rational {
public:
  Rational operator-() const { /* IMPLEMENT */ }
  Rational operator+() const { return *this; }

  Rational invert() const { /* IMPLEMENT */ } //for division

  Rational& operator+=(Rational const& rhs) { /* IMPLEMENT */ } 
  Rational& operator-=(Rational const& rhs) { return *this += -rhs; }
  Rational& operator*=(Rational const& rhs) { /* IMPLEMENT */ }
  Rational& operator/=(Rational const& rhs) { return *this *= invert(rhs); }
};

Rational operator+(Rational const& lhs, Rational const& rhs) { 
  Rational tmp(lhs); 
  return tmp += rhs; 
}
Rational operator-(Rational const& lhs, Rational const& rhs) { 
  Rational tmp(lhs); 
  return tmp -= rhs; 
}
Rational operator*(Rational const& lhs, Rational const& rhs) { 
  Rational tmp(lhs); 
  return tmp *= rhs; 
}
Rational operator/(Rational const& lhs, Rational const& rhs) { 
  Rational tmp(lhs); 
  return tmp /= rhs; 
}

bool operator==(Rational const& lhs, Rational const& rhs) { 
  /* IMPLEMENT */ 
}
bool operator!=(Rational const& lhs, Rational const& rhs) { 
  return !(lhs == rhs); 
}
bool operator<(Rational const& lhs, Rational const& rhs) { 
  /* IMPLEMENT */ 
}
bool operator>(Rational const& lhs, Rational const& rhs) { 
  return rhs < lhs; 
}
bool operator<=(Rational const& lhs, Rational const& rhs) { 
  return !(lhs > rhs); 
}
bool operator>=(Rational const& lhs, Rational const& rhs) { 
  return !(lhs < rhs); 
}

So it’s not that bad, is it? A few one-liners and we are done. Everything is consistent, so what is there left to do?

Simplify Your Work

Developers are lazy. If there is something the computer can do for us, that’s the way to do it. We don’t want to write all those one-liners by hand each time we implement a class that has an overloaded operator. They would look the same each and every time, so it should be automated.

The Boost libraries provide Boost.Operators, which does exactly that automation for us and spares us the tedious typing. Our little class then would look like this:

#include <boost/operators.hpp>

class Rational : boost::ordered_field_operators<Rational> 
{
public:
  Rational operator-() const { /* IMPLEMENT */ }
  Rational operator+() { return *this; };

  Rational invert() const { /* IMPLEMENT */ }

  Rational& operator+=(Rational const& rhs) { /* IMPLEMENT */ } 
  Rational& operator-=(Rational const& rhs) { return *this += -rhs; }
  Rational& operator*=(Rational const& rhs) { /* IMPLEMENT */ }
  Rational& operator/=(Rational const& rhs) { return *this *= invert(rhs); }
};

bool operator==(Rational const& lhs, Rational const& rhs) { /* IMPLEMENT */ }
bool operator<(Rational const& lhs, Rational const& rhs) { /* IMPLEMENT */ }

We are basically back to the seven operators we declared in the beginning, except for the additional unary `operator+`, and that the four basic arithmetic operations are replaced by `operator+=` etc.  Each of the other operators is provided by simply deriving from a single template class.

“Do as the ints do”: The Concept of Boost.Operators

Boost.Operators is designed to generate the operators which would have to be implemented the same way every time, if the classes and operators are to behave like one would expect it from standard data types. Basically this applies for most of the “usual implementations” I mentioned in the “Common Practice” post.

For those operators, almost every work is done for us by Boost.Operators, and we have to write only one or two simple lines. Of course that means we should not write exotic operators, unless we want to implement everything by hand. But since our code should be clear and maintainable, such exotic operators don’t occur that often.

Operator Families

Boost defines one or more template for each operator family. For each family we have to implement one base operator which defines the behavior for the other operators. For the operators to work, the base operators need to have the usual signature, e.g. comparison operators have return type bool or convertible to bool.

name base operator generated operators
`less_than_comparable` < >, <= , >=
`equality_comparable` == !=
`addable` += +
`subtractable` -= (needs copy ctor)
`multiplicable` *= * (needs copy ctor)
`dividable` /= / (needs copy ctor)
`modable` %= % (needs copy ctor)
`orable` |= | (needs copy ctor)
`andable` &= & (needs copy ctor)
`xorable` ^= ^ (needs copy ctor)
`incrementable` ++(pre) ++(post) (needs copy ctor)
`decrementable` –(pre) –(post) (needs copy ctor)
`right_shiftable` >>= >> (needs copy ctor)
`left_shiftable` <<= << (needs copy ctor)
`equivalent` < ==
`partially_ordered` <, == <, <=, >, >=
`dereferencable` unary * ->
`indexable` unary *, +(T, D) []

The families for arithmetic and bitwise operators are self-explanatory. The last two families, `dereferencable` and `indexable` generate operators for pointers or iterators. The special notation of `operator+` for `indexable` means that the operands are of different type; the first operand is the pointer, the second an index. `indexable` generates an `operator[]` for the pointer class, so that `ptr[n] == *(ptr+n)`.

In the next post I will write about how those operator families are further composed to operator groups like the `ordered_field_operators` shown in the example, and some more in-depth examples on how Boost.Operators can be used.

Facebooktwittergoogle_plusredditlinkedinFacebooktwittergoogle_plusredditlinkedinby feather

Leave a Reply