Bytebook Compiler

Inheritance & Polymorphism

Operator Overloading

Make your own types work with +, ==, <<, [] and more: member vs friend operators, unary and binary operators, and the rules you must follow.

5 min read4 runnable examples4-question quiz

Operator overloading lets you define what operators mean for your own classes, so code reads naturally: c1 + c2 instead of add(c1, c2). It is a form of compile-time polymorphism.

An overloaded operator is just a function named operator followed by the symbol: operator+, operator==, operator<<.

Binary operators as members

For a + b written as a member function, a is the object the function is called on and b is the parameter.

C++
#include <iostream>
using namespace std;

class Complex {
    double re, im;
public:
    Complex(double r = 0, double i = 0) : re(r), im(i) {}
    Complex operator+(const Complex &o) const { return Complex(re + o.re, im + o.im); }
    Complex operator-(const Complex &o) const { return Complex(re - o.re, im - o.im); }
    Complex operator*(const Complex &o) const {
        return Complex(re * o.re - im * o.im, re * o.im + im * o.re);   // (a+bi)(c+di)
    }
    bool operator==(const Complex &o) const { return re == o.re && im == o.im; }
    void print() const { cout << re << (im < 0 ? " - " : " + ") << (im < 0 ? -im : im) << "i" << endl; }
};

int main() {
    Complex a(3, 2), b(1, -4);
    Complex sum = a + b;           // calls a.operator+(b)
    sum.print();
    (a - b).print();
    (a * b).print();
    cout << boolalpha << (a + b == Complex(4, -2)) << endl;
    return 0;
}
Output
4 - 2i
2 + 6i
11 - 10i
true

Unary operators

Prefix and postfix ++ are told apart by a dummy int parameter on the postfix version:

C++
#include <iostream>
using namespace std;

class Counter {
    int value;
public:
    Counter(int v = 0) : value(v) {}
    Counter &operator++()   { ++value; return *this; }                 // prefix: ++c
    Counter operator++(int) { Counter old = *this; ++value; return old; }  // postfix: c++
    Counter operator-() const { return Counter(-value); }              // unary minus
    int get() const { return value; }
};

int main() {
    Counter c(5);
    Counter a = c++;     // a gets 5, c becomes 6
    Counter b = ++c;     // c becomes 7, b gets 7
    cout << a.get() << " " << b.get() << " " << c.get() << " " << (-c).get() << endl;
    return 0;
}
Output
5 7 7 -7

Overloading << and >> with friends

cout << obj has cout (an ostream) on the left, so it cannot be a member of your class. Write it as a non-member — usually a friend so it can read private data — and return the stream to allow chaining:

C++
#include <iostream>
using namespace std;

class Fraction {
    int num, den;
    static int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
    void reduce() { int g = gcd(num < 0 ? -num : num, den); if (g) { num /= g; den /= g; } }
public:
    Fraction(int n = 0, int d = 1) : num(n), den(d) { reduce(); }
    Fraction operator+(const Fraction &o) const { return Fraction(num * o.den + o.num * den, den * o.den); }
    friend ostream &operator<<(ostream &out, const Fraction &f) { return out << f.num << "/" << f.den; }
    friend istream &operator>>(istream &in, Fraction &f) { in >> f.num >> f.den; f.reduce(); return in; }
};

int main() {
    Fraction a(1, 2), b;
    cout << "Enter a fraction (numerator denominator): ";
    cin >> b;
    cout << a << " + " << b << " = " << a + b << endl;
    cout << "6/8 simplifies to " << Fraction(6, 8) << endl;
    return 0;
}
Output
Enter a fraction (numerator denominator): 7 3
1/2 + 7/3 = 17/6
6/8 simplifies to 3/4
Sample input is includedTry it Yourself

Subscript and comparison operators

C++
#include <iostream>
#include <stdexcept>
using namespace std;

class SafeArray {
    int data[5] = {10, 20, 30, 40, 50};
public:
    int &operator[](int i) {                     // returns a reference so a[i] = x works
        if (i < 0 || i >= 5) throw out_of_range("index " + to_string(i));
        return data[i];
    }
};

int main() {
    SafeArray a;
    a[2] = 99;
    cout << a[2] << endl;
    try { cout << a[7] << endl; }
    catch (const out_of_range &e) { cout << "caught bad " << e.what() << endl; }
    return 0;
}
Output
99
caught bad index 7

Rules

  • You cannot create new operators or change precedence, associativity or the number of operands.
  • At least one operand must be a user-defined type (you cannot redefine int + int).
  • These cannot be overloaded: :: . .* ?: sizeof typeid.
  • =, (), [] and -> must be member functions.
  • << and >> for streams must be non-members (usually friends).
Member operator Friend / non-member operator
left operand is *this both operands are parameters
one parameter for binary operators two parameters for binary operators
left operand must be your class left operand can be any type (cout << x, 5 + x)

Test yourself

4 questions · pick an answer to check it instantly

  1. 1

    How many parameters does a member operator+ take?

  2. 2

    Why must operator<< for cout << obj be a non-member?

  3. 3

    Which operator cannot be overloaded?

  4. 4

    How does C++ distinguish postfix operator++ from prefix?