This post is part of a series of tutorials on C++

Arithmetic operators allow you to perform basic expressions with instances of C++ objects, such as addition, and subtraction. These can be useful in a variety of settings, in particular when your C++ classes have natural definitions that you’d like to use and chain together.

Let’s focus first on binary arithmetic operators. Most every C++ operator that comes in the form A = B # C can be overloaded, where # is a placeholder for the operator in question. One possible signature for such operators, using addition (+) as an example, is:

class Foo {
  Foo operator+(const Foo& rhs) const
  {
    Foo ret;
    // actual addition of rhs to this, returning the new copy, ret, we created;
    return ret;
  };
};

You might at first think this is OK (and it is). We’ve appropriately using a const reference to pass in the input, since we have no intention of changing rhs, and we will then add it to our current member, without changing it. We then create a new object, ret that is there sum and return it by copy. In practice, when executing your code you would do something like this:

int main() {
  Foo a,b,c;
  c = a + b;
}

The line c = a + b is deceptively complex. a+b translates to a.operator+(b) which then returns a copy of an object and then c = ... calls the assignment operator to copy this object into c. That’s quite a lot of work, it turns out. While this is all valid C++ code, the thing to remember is that you’re overloading a function, which means that there can be many valid signatures. Consider the following:

class Foo {
  Foo operator+(int rhs) const
  {
    Foo ret;
    // add an int to a Foo???;
    return ret;
  };
};

Which might seem far fetched! Why would we want to add a int to a Foo? Well, let’s give a more concrete example:

class RationalNumber {
  int num;
  int denom;

  RationalNumber operator+(int rhs) const
  {
    RationalNumber ret;
    ret.denom = this.denom;
    ret.num = this->num + rhs*this->denom;
    return ret;
  };
};

In the above example, RationalNumber stores a fraction, \(\frac{\textrm{num}}{\textrm{denom}}\) without using floating point values. If one wants to add another integer value, rhs to this fraction, one then adds rhs copies of the denominator, denom.

We can then right expressions like:

int main() {
  RationalNumber a, b;
  a.num = 3; a.denom = 5;  // a stores 3/5
  b = a + 5;               // b stores 28/5
}

Which is kind of handy. Since operators can be overloaded, we can do all sorts of interesting things with them! Unfortunately, with the above code, we run into a nasty issue:

int main() {
  RationalNumber a, b;
  a.num = 3; a.denom = 5;  // a stores 3/5
  b = 5 + a;               // compile error!
}

The above code fails to compile because of how operators are expanded. 5 + a is equivalent to 5.operator+(a), but 5 is a literal, not an instance of a class. It has no member functions can of course we can’t using the . operator on it. The solution is, unfortunately, kind of crude:

class RationalNumber {
  int num;
  int denom;

  RationalNumber operator+(int rhs) const;
  friend RationalNumber operator+(int lhs, const RationalNumber& rhs);
};
//class member
RationalNumber RationalNumber::operator+(int rhs) const {
  RationalNumber ret;
  ret.denom = this.denom;
  ret.num = this->num + rhs*this->denom;
  return ret;
};
//friend
RationalNumber operator+(int lhs, const RationalNumber& rhs) {
  RationalNumber ret;
  ret.denom = rhs.denom;
  ret.num = rhs.num + lhs*rhs.denom;
  return ret;
}

We’re forced to use friend functions. In C++, the friend keyword allows a non-member function to access class members. Note that while the first operator+() is defined as a class member, and thus has the class scoping RationalNumber::, the second operator+() is just defined as a standalone function (and thus, for example, cannot access this). It’s not horrible, but the duality between the two is suboptimal. Still, in this case we’re defining an asymmetric version of +, since the types of the lhs and rhs are not the same. For our purposes, the major instance where you might consider doing things this way is to scale a three-dimensional vector by a scalar. Nevertheless, if you don’t want to code the friend function, you can always just make sure that you multiply by scalars on the right, but that’s not quite as user friendly.

See Arithmetic Operators, Part 2 for more info.



Back to C++ Tutorials