Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

3.4 Operator Overloading — Unary, Binary & with Friend Functions

Lesson 16 of 22 in the free Object Oriented Programming with C++ notes on Siksha Sarovar, written by Rohit Jangra.

3.4 Operator Overloading — Unary, Binary & with Friend Functions

What is Operator Overloading?

Operator overloading lets you redefine operator symbols (+, -, *, ==, etc.) for user-defined types. The same + that adds two ints can be made to "add" two Complex numbers.

Complex c1(3, 4), c2(1, 2);
Complex c3 = c1 + c2;       // operator+ is overloaded

This is a form of compile-time polymorphism.

---

Why Overload Operators?

BenefitExample
Readable codec1 + c2 vs c1.add(c2)
Natural syntax for math typesComplex, Vector, Matrix, BigInt
Consistent with built-insUser-defined types feel "native"
Stream supportcout << myObj
STL compatibilityContainers, algorithms expect <, ==, etc.

---

Operators That CAN Be Overloaded

CategoryOperators
Arithmetic+ - * / %
Relational== != < > <= >=
Logical`&&!`
Bitwise`&^ ~ << >>`
Assignment`= += -= *= /= %= &== ^= <<= >>=`
Increment / Decrement++ --
Subscript[]
Function call()
Member access-> ->* ,
Memorynew new[] delete delete[]
Stream<< >> (friend functions usually)

---

Operators That CANNOT Be Overloaded

OperatorWhy
. (member access)Reserved
.* (member-pointer access)Reserved
:: (scope resolution)Compile-time
?: (ternary)Conditional, control flow
sizeofCompile-time
typeidRTTI
# ##Preprocessor
Memorise: "Cannot overload dot, dot-star, scope, ternary, sizeof".

---

Rules of Operator Overloading

  1. At least one operand must be a user-defined type (can't overload + for two ints)
  2. Cannot create new operators — only the existing ones
  3. Cannot change operator precedence or associativity
  4. Cannot change number of operands (unary stays unary, binary stays binary)
  5. Some operators must be member functions (=, [], (), ->)
  6. Some operators are typically friend functions (<<, >> for streams)

---

Two Ways to Overload

1. As a member function

The left operand must be the object on which the operator is called:

class Complex {
private:
    double real, imag;

public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    Complex operator+(const Complex& other) const {     // member function
        return Complex(real + other.real, imag + other.imag);
    }

    void display() {
        cout << real << " + " << imag << "i" << endl;
    }
};

int main() {
    Complex c1(3, 4), c2(1, 2);
    Complex c3 = c1 + c2;        // c1.operator+(c2)
    c3.display();                 // 4 + 6i
    return 0;
}

The expression c1 + c2 is internally interpreted as c1.operator+(c2).

2. As a friend (or non-member) function

If the left operand is not your class — e.g., int + Complex — you need a friend (or non-member) function:

class Complex {
private:
    double real, imag;

public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    friend Complex operator+(const Complex& a, const Complex& b);
    friend Complex operator+(double scalar, const Complex& c);     // double + Complex
    friend Complex operator+(const Complex& c, double scalar);     // Complex + double
};

Complex operator+(const Complex& a, const Complex& b) {
    return Complex(a.real + b.real, a.imag + b.imag);
}

Complex operator+(double scalar, const Complex& c) {
    return Complex(scalar + c.real, c.imag);
}

Complex operator+(const Complex& c, double scalar) {
    return Complex(c.real + scalar, c.imag);
}

int main() {
    Complex c(3, 4);
    Complex r1 = c + 5;       // calls Complex+double
    Complex r2 = 5 + c;       // calls double+Complex
    return 0;
}
Best practice: Define arithmetic binary operators as non-member friends for symmetry (handles both sides cleanly). Define modifying operators (+=, -=, =) as members.

---

Unary Operator Overloading

Unary operators act on a single operand: -x, ++x, x++, !x, ~x.

Unary minus

class Vector {
public:
    int x, y;

    Vector(int a, int b) : x(a), y(b) {}

    Vector operator-() const {        // unary minus
        return Vector(-x, -y);
    }
};

Vector v(3, 4);
Vector negV = -v;        // (-3, -4)

Prefix vs Postfix ++ / --

class Counter {
private:
    int count;

public:
    Counter() : count(0) {}

    // Prefix ++c
    Counter& operator++() {
        ++count;
        return *this;
    }

    // Postfix c++ — dummy int parameter to differentiate
    Counter operator++(int) {
        Counter temp = *this;  // save current state
        ++count;
        return temp;            // return OLD value
    }

    int getCount() const { return count; }
};

int main() {
    Counter c;
    ++c;        // prefix: count = 1, returns ref to current c
    c++;        // postfix: returns OLD c (count=1), then count becomes 2
    cout << c.getCount();  // 2
    return 0;
}

Logical NOT !

class Smart {
    bool flag;
public:
    bool operator!() const { return !flag; }
};

---

Binary Operator Overloading

Binary operators act on two operands: a + b, a == b, a < b.

Arithmetic

Complex Complex::operator+(const Complex& other) const {
    return Complex(real + other.real, imag + other.imag);
}

Complex Complex::operator-(const Complex& other) const {
    return Complex(real - other.real, imag - other.imag);
}

Complex Complex::operator*(const Complex& other) const {
    return Complex(real*other.real - imag*other.imag,
                   real*other.imag + imag*other.real);
}

Compound assignment

Complex& Complex::operator+=(const Complex& other) {
    real += other.real;
    imag += other.imag;
    return *this;
}
Returns reference to *this — enables chaining a += b += c.

Comparison

bool Complex::operator==(const Complex& other) const {
    return real == other.real && imag == other.imag;
}

bool Complex::operator!=(const Complex& other) const {
    return !(*this == other);
}

bool Complex::operator<(const Complex& other) const {
    return (real*real + imag*imag) < (other.real*other.real + other.imag*other.imag);
}

Stream insertion / extraction (typically friend)

class Complex {
    double real, imag;
public:
    Complex(double r, double i) : real(r), imag(i) {}

    friend ostream& operator<<(ostream& os, const Complex& c);
    friend istream& operator>>(istream& is, Complex& c);
};

ostream& operator<<(ostream& os, const Complex& c) {
    os << c.real << " + " << c.imag << "i";
    return os;       // return ostream& enables chaining: cout << c1 << c2;
}

istream& operator>>(istream& is, Complex& c) {
    cout << "Enter real: ";
    is >> c.real;
    cout << "Enter imag: ";
    is >> c.imag;
    return is;
}

int main() {
    Complex c(3, 4);
    cout << c << endl;     // "3 + 4i"

    Complex c2(0, 0);
    cin >> c2;
    cout << "You entered: " << c2 << endl;
    return 0;
}
<< and >> must be friend functions (or non-members) because the left operand is ostream / istream, not your class.

---

Operator Overloading with Friend Functions

When the left operand is not your class type — like double + Complex — you need a friend or non-member function.

class Complex {
    double real, imag;
public:
    Complex(double r, double i) : real(r), imag(i) {}

    friend Complex operator+(double scalar, const Complex& c);
};

Complex operator+(double scalar, const Complex& c) {
    return Complex(scalar + c.real, c.imag);
}

Complex r = 5.0 + Complex(2, 3);  // 7 + 3i — uses friend

This is why << for ostream is always a friend — the left operand is ostream, which you don't own.

---

Special Operator Overloads

= (Assignment) — must be a member

Complex& Complex::operator=(const Complex& other) {
    if (this != &other) {
        real = other.real;
        imag = other.imag;
    }
    return *this;
}

= cannot be a friend; the compiler generates a default if you don't.

[] (Subscript) — must be a member

class Array {
    int data[100];
public:
    int& operator[](int i) {
        return data[i];        // returns reference — allows arr[5] = 10;
    }

    int operator[](int i) const {     // const version
        return data[i];
    }
};

Array a;
a[5] = 10;                  // OK — uses non-const version
int x = a[5];                // OK — uses const version (if a is const)

() (Function call) — functors

class Adder {
public:
    int operator()(int a, int b) { return a + b; }
};

Adder add;
int s = add(3, 5);    // 8 — looks like a function call

These are functors — objects that act like functions. Heavily used in STL (std::sort with comparators).

-> (Member access) — for smart pointers

class SmartPtr {
    Resource* p;
public:
    Resource* operator->() { return p; }
};

SmartPtr sp;
sp->use();    // calls operator->() then ->use()

This is how std::unique_ptr / std::shared_ptr work.

---

Common Operator Overloading Patterns

class Money {
    long paise;     // 100 paise = ₹1

public:
    Money(long p = 0) : paise(p) {}

    // Arithmetic
    Money operator+(const Money& m) const { return Money(paise + m.paise); }
    Money operator-(const Money& m) const { return Money(paise - m.paise); }
    Money operator*(int n) const { return Money(paise * n); }
    Money& operator+=(const Money& m) { paise += m.paise; return *this; }

    // Comparison
    bool operator==(const Money& m) const { return paise == m.paise; }
    bool operator<(const Money& m) const { return paise < m.paise; }
    bool operator>(const Money& m) const { return paise > m.paise; }

    // Unary
    Money operator-() const { return Money(-paise); }

    // Stream
    friend ostream& operator<<(ostream& os, const Money& m) {
        os << "₹" << (m.paise / 100.0);
        return os;
    }
};

int main() {
    Money a(50000), b(15000);     // ₹500 and ₹150
    Money sum = a + b;             // ₹650
    cout << sum << endl;            // ₹650
    if (a > b) cout << "a is more" << endl;
    return 0;
}

---

Member vs Friend / Non-member — exam table

OperatorPreferred formWhy
= [] () ->MemberRequired by language
**Arithmetic (+ - /)*Non-member / friendSymmetric — handles a + 5 and 5 + a
Compound assignment (+= -=)MemberModifies this
Comparison (== !=)Non-memberSymmetric
<< >>Friend (non-member)Left operand is stream
Unary (-, !, ~)Member or non-memberEither works
++ --MemberModifies this

---

Study deep

  1. Operator overloading is sugar, not magic. c1 + c2 compiles to operator+(c1, c2) or c1.operator+(c2). Once you grasp that, the rules become obvious.
  1. Maintain expected semantics. + should add (commutative-ish). == should be equivalence. < should be a total order. Surprising users with weird semantics is worse than not overloading at all.
  1. Provide == and < for STL. Many STL algorithms need them. std::set needs <. Containers compare with ==. Define them or your class won't work with STL.
  1. C++20 spaceship operator <=>. Modern alternative — define one operator and the compiler auto-generates <, <=, >, >=, ==, !=:
auto operator<=>(const T& other) const = default;
  1. Operator overloading is restricted in some languages. Java, Go, Rust limit it (Java forbids; Go forbids; Rust allows via traits). C++ and Python are permissive. The trade-off: power vs potential for abuse.

Key Terms — Lesson 3.4

The terms below cover operator overloading — every PYQ on the topic expects fluent use.

Operator Overloading — Giving custom meanings to existing C++ operators (+, -, <<, ==, [], etc.) when applied to user-defined types. c1 + c2 where c1 and c2 are Complex instances calls a user-defined operator+. A specific case of function overloading.

operator Keyword — The C++ keyword used to declare an overloaded operator. Complex operator+(const Complex& other) const; declares the overloaded + operator as a member. operator+(left, right) declares it as a non-member.

Rules / Restrictions of Operator Overloading — (1) At least one operand must be a user-defined type — you cannot overload operators for two built-in types. (2) Cannot create new operators (@, are illegal). (3) Cannot change operator precedence or associativity. (4) Cannot change the arity (number of operands). (5) Some operators must be member functions** (=, [], (), ->).

Operators That Cannot Be Overloaded — Five operators: :: (scope resolution), . (member access), **. (pointer-to-member access), ?: (ternary), sizeof, and typeid*. These have special compile-time meaning that the language reserves.

Unary Operator Overloading — Overloading operators that take one operand-x, ++x, x++, !x, ~x, p, &x. As a member, takes no parameter (the operand is this). As a non-member / friend, takes one parameter.

Binary Operator Overloading — Overloading operators that take two operandsa + b, a < b, a == b, a << b. As a member, takes one parameter (the right operand; *this is the left). As a non-member / friend, takes two parameters.

Friend-Based Operator Overloading — Implementing an overloaded operator as a non-member function with friend access. Required for operators where the left operand is not the user's class — most importantly operator<< (left operand is ostream) and arithmetic operators where conversion from a built-in to the class type should work on either side (5 + complex).

Member vs Non-Member Operator — Two ways to implement an overloaded operator. Member functions have implicit this as the left operand; non-members (often friends) take both operands explicitly. Non-members give symmetric behaviour — complex + 5 and 5 + complex both work.

Pre-Increment vs Post-Increment Overloading — Two related but distinct operators. Pre-increment ++x is overloaded as T& operator++(); — returns reference to incremented self. Post-increment x++ is overloaded as T operator++(int); — the dummy int parameter distinguishes it; returns a copy of the original value before increment.

Stream Insertion Operator (<<) Overloading — Custom << for a user-defined class: friend ostream& operator<<(ostream& os, const Complex& c);. Lets cout << complex work. Almost always a friend because the left operand is ostream. Returns the stream by reference to enable chaining (cout << a << b).

Stream Extraction Operator (>>) Overloading — Custom >> for a user-defined class: friend istream& operator>>(istream& is, Complex& c);. Lets cin >> complex work. Same friend reasoning as <<.

Assignment Operator (=) Overloading — Custom assignment: Complex& operator=(const Complex& other);. Must be a member function (cannot be a non-member). The compiler synthesises a member-wise version if you don't write one; classes that manage raw resources should write their own (Rule of Three).

Compound Assignment (+=, -=, etc.) — Operators that combine arithmetic with assignment. Implementing += is often a useful starting point: write += first, then implement + in terms of += for consistency.

Subscript Operator ([]) Overloading — Custom [] to make a class indexable like an array: T& operator[](size_t i);. Must be a member function. Often provide both a non-const and a const version.

Function Call Operator (()) Overloading — Custom () makes an object callable like a function: int operator()(int x) const;. Objects implementing () are called functors / function objects and are the historical predecessor of C++11 lambdas.

Arrow Operator (->) Overloading — Custom -> lets a class behave like a pointer: T operator->();. Used by smart pointers, iterators, optional types — (sptr).method() and sptr->method() both work because of -> overloading.

Functor / Function Object — An object of a class that overloads operator(), making it usable wherever a callable is expected. Functors can carry state (unlike free functions). Pre-C++11, functors were the primary way to write inline callbacks for STL algorithms.

Spaceship Operator (<=>) (C++20) — A modern operator that performs three-way comparison — returns <0, 0, or >0 like strcmp. Define operator<=> once and the compiler auto-generates <, <=, >, >=, ==, != for you. The cleanest way to add full comparison support to a class.

Conversion Operator — A special member function that converts the class type to another type: operator int() const { return value; } lets a Complex behave as an int in some contexts. Powerful but error-prone; modern style marks them explicit to require explicit conversion.

Mutating vs Non-Mutating Operators — Operators like +=, ++ modify the object and typically return T&. Operators like +, -, * do not modify and return a new value. Get this distinction wrong and your overloads will surprise users.

Chaining (Operator Return-Type Convention) — To support a = b = c or cout << a << b, the overloaded operator must return a reference to the appropriate object (typically *this or the left operand). Return-by-value breaks chaining.

Semantics Preservation — The cardinal rule of operator overloading: make the operator do what users intuitively expect. + should add (commutative-ish); == should be equivalence; < should define a total ordering; << should output. Surprising semantics is worse than not overloading at all.

---

PYQ pattern (very common): "What is operator overloading? Overload + operator for a Complex number class." — Define; show Complex class with real + imag; member-function or friend-function form; demonstrate c3 = c1 + c2.
PYQ pattern: "Differentiate operator overloading using member function and friend function." — Member: a + b becomes a.operator+(b); works for Complex + int only if Complex is left. Friend: works for both Complex + int and int + Complex. List operators that MUST be members (= [] () ->).
PYQ pattern: "Overload pre-increment and post-increment operators for a Counter class." — Define class; prefix returns Counter&; postfix takes dummy int and returns Counter by value.
PYQ pattern: "List the rules / restrictions of operator overloading." — 5 rules (at least one user-defined operand; can't create new; can't change precedence; can't change arity; some must be members).