CSS Prepare

optional · CSS study notes

7. Operator Overloading

  • Redefining the behavior of an operator (+, -, ==, etc.) for user-defined types
  • Stream insertion (<<) / extraction (>>): overloaded to enable custom objects to work with cout/cin
  • Unary operator (one operand, e.g., -x, ++x) vs Binary operator (two operands, e.g., x + y) overloading follow different signatures
class Complex
{
private:
    double real, imag;
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
    Complex operator+(const Complex& other)
    {
        return Complex(real + other.real, imag + other.imag);
    }
    friend ostream& operator<<(ostream& out, const Complex& c)
    {
        out << c.real << " + " << c.imag << "i";
        return out;
    }
};
// Complex a(3,4), b(1,2); cout << (a + b);  --> 4 + 6i

Operators that CANNOT be overloaded: :: (scope resolution), . (member access), .* (pointer-to-member access), ?: (ternary), sizeof

Why overload operators at all?

Without overloading, combining two objects requires an awkward function call: addComplex(a, b) instead of the natural a + b. Overloading lets user-defined types read and behave like built-in types.

Full worked example, with comparison operator too

#include <iostream>
using namespace std;

class Complex
{
private:
    double real, imag;

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

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

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

    friend ostream& operator<<(ostream& out, const Complex& c);   // needs friend -- see below
};

ostream& operator<<(ostream& out, const Complex& c)
{
    out << c.real << " + " << c.imag << "i";
    return out;
}

int main()
{
    Complex a(3, 4), b(1, 2);
    Complex sum = a + b;              // calls operator+
    cout << sum << endl;              // 4 + 6i, calls operator<<
    cout << (a == b) << endl;         // 0 (false)
    return 0;
}

Why operator<< must be a friend, not a member

a + b works as a member function because the left operand (a) is the object calling operator+. But cout << c puts cout (an ostream) on the LEFT — you can't add a member function to the built-in ostream class, so operator<< must be a free function with friend access to Complex's private data.

Operators that CANNOT be overloaded

:: (scope resolution), . (member access), .* (pointer-to-member access), ?: (ternary), sizeof — these are fixed by the language and always operate on their literal operands, not on overloaded semantics.

Important MCQs

  1. Which operator CANNOT be overloaded in C++?
    • a) + b) == c) :: (scope resolution) d) <<
  2. Function/operator overloading is an example of:
    • a) Runtime polymorphism b) Compile-time polymorphism c) Inheritance d) Encapsulation
  3. operator<< for a custom class is typically implemented as:
    • a) A private member function b) A friend (non-member) function c) A static member d) A constructor
  4. Overloading operator+ between two Complex objects lets you write:
    • a) add(a,b) only b) a + b directly, like built-in types c) Nothing different d) Only integer addition

✅ Self-Test Questions (with Answers)

Q1. Why does operator+ work as a member function but operator<< cannot?

Ans: Because of a single rule: for a binary operator written as a member function, the left-hand operand must be the object of that class — it becomes the invoking object, *this, and only the right-hand operand is passed as a parameter. In a + b the left operand a is a Complex, so a.operator+(b) works perfectly as a member. But in cout << c the left operand is cout, an ostream — not a Complex. Making it a member would require adding a member function to the standard library's ostream class, which you cannot modify. The only option is a free (non-member) function taking the stream as its first parameter; since it must read Complex's private data, it is declared friend.

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

    // MEMBER: left operand is a Complex -> becomes *this
    Complex operator+(const Complex& o) const {        // a + b  ==  a.operator+(b)
        return Complex(real + o.real, imag + o.imag);
    }

    // FREE + friend: left operand is the STREAM, not a Complex
    friend ostream& operator<<(ostream& out, const Complex& c);
};

ostream& operator<<(ostream& out, const Complex& c) {   // cout << c == operator<<(cout, c)
    out << c.real << " + " << c.imag << "i";
    return out;                                          // returning the stream enables CHAINING
}

int main() {
    Complex a(3, 4), b(1, 2);
    cout << (a + b) << '\n';       // 4 + 6i
    cout << a << " and " << b;     // 3 + 4i and 1 + 2i   <- chaining works because of ostream&
}

Two further consequences worth stating: (1) operator<< must return ostream&, not void, precisely so that cout << a << b can chain — the result of the first call becomes the left operand of the second. (2) A second, more subtle advantage of the free-function form is symmetric implicit conversion: as a member, a + 2.0 compiles (the right operand converts via the constructor) but 2.0 + a does not, because a double has no operator+ for Complex. Written as a free function, Complex operator+(const Complex&, const Complex&), both operands can be converted and both expressions work — which is why arithmetic operators are often best written as non-members too. operator>> follows the same pattern, taking istream& and a non-const object reference since it must modify it.

Q2. What would happen if Complex didn't overload operator== and you tried if (a == b)?

Ans: The code would fail to compile, with an error along the lines of "no match for operator== (operand types are Complex and Complex)". C++ does not generate a comparison operator for you (unlike the copy constructor, copy assignment operator and destructor, which are implicitly generated). The compiler has no way to guess what equality should mean for your type — whether to compare every member, only some, or with a tolerance for floating-point values — so it refuses rather than guessing. Note this is a compile-time error, caught at the point of misuse, not a run-time surprise.

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

Complex a(3, 4), b(3, 4);
// if (a == b) { }     // ERROR: no match for 'operator==' (operand types are
//                     //        'Complex' and 'Complex')

// The fix -- supply the meaning yourself (const-correct, by const reference):
class Complex2 {
    double real, imag;
public:
    Complex2(double r = 0, double i = 0) : real(r), imag(i) {}
    bool operator==(const Complex2& o) const { return real == o.real && imag == o.imag; }
    bool operator!=(const Complex2& o) const { return !(*this == o); }  // define in terms of ==
};
Complex2 x(3, 4), y(3, 4);
cout << (x == y);      // 1  (true)

Points that earn marks: == and != are independent — before C++20, defining one does not give you the other, so both must be written (idiomatically, != is implemented as !(*this == o)). Mark the operator const and take the parameter by const& so it works on const objects and avoids a copy. A correctness caveat specific to this example: comparing doubles with == is unreliable because of floating-point rounding, so a real Complex should compare within an epsilon. Finally, since C++20 you may write bool operator==(const Complex&) const = default; and the compiler will generate member-wise equality (and derive != automatically).

Q3. Name two operators C++ does not allow you to overload.

Ans: Two clear examples are the scope-resolution operator :: and the sizeof operator. The complete list of operators that cannot be overloaded in C++ is:

OperatorNameWhy it cannot be overloaded
::Scope resolutionIts operands are names/scopes, not values; resolved entirely at compile time
.Member accessOverloading it would make every member name ambiguous
.*Pointer-to-member accessSame reason as the dot operator
?:Ternary conditionalRequires short-circuit evaluation of only one branch, which a function call cannot reproduce
sizeofSize queryA compile-time property of a type, fixed by the language
typeidType identificationBuilt-in RTTI facility
alignofAlignment queryCompile-time type property
class X {
public:
    X operator+(const X&);        // OK: + can be overloaded
    // X operator::(const X&);    // ERROR: :: cannot be overloaded
    // size_t operator sizeof();  // ERROR: sizeof cannot be overloaded
    // X operator?:(bool, X, X);  // ERROR: ?: cannot be overloaded
    X* operator->();              // OK -- note the ARROW can be overloaded
};                                // (smart pointers rely on this)

The classic MCQ trap is the pair . and ->: the dot cannot be overloaded, but the arrow -> can — that is precisely how unique_ptr and shared_ptr work. Likewise sizeof cannot be overloaded while new, delete, (), [], , and even &&/|| can (though overloading the last two is discouraged, since it silently destroys short-circuiting).

Further rules on what overloading cannot change: you cannot invent a new operator symbol (no ** for power), cannot alter an operator's arity (binary + stays binary), cannot change its precedence or associativity, and at least one operand must be a user-defined type — so you can never redefine int + int.

📌 Sample & Repeated FPSC Questions (2016–2026)

2025, Paper I, Q5(a) (10 marks) — Demonstrate the use of stream insertion and extraction operators for creating custom objects. Provide a suitable coding example for illustrating the functional difference between them.

operator<< sends an object's data out to a stream (cout, a file); operator>> reads data in from a stream into an object. Both must be overloaded as free functions (usually friend) because the left-hand operand is the stream (ostream/istream), not the class itself — you can't add a member function to a built-in stream class.

class Point
{
    int x, y;
public:
    Point(int a = 0, int b = 0) : x(a), y(b) {}

    friend ostream& operator<<(ostream& out, const Point& p)
    {
        out << "(" << p.x << ", " << p.y << ")";
        return out;                 // enables chaining: cout << p1 << p2;
    }
    friend istream& operator>>(istream& in, Point& p)
    {
        in >> p.x >> p.y;
        return in;
    }
};

Key reasoning points:

  • operator<< takes the object by const& (only reads it) and returns ostream&.
  • operator>> takes the object by non-const& (it must modify it) and returns istream&.
  • Both return a stream reference specifically so calls can be chained: cout << p1 << p2;.

2023, Paper I, Q3(b) (6 marks) — Write a note on the use of overloaded operators. Discuss with examples.

Operator overloading lets a class redefine standard operators (+, -, ==, <<, …) so objects of a user-defined type can be manipulated with natural mathematical/logical notation instead of explicit function calls (addComplex(a,b) → a + b). It must preserve the operator's usual arity (a binary + still takes two operands) and is resolved at compile time based on operand types.

class Complex
{
    double re, im;
public:
    Complex(double r = 0, double i = 0) : re(r), im(i) {}
    Complex operator+(const Complex &c) const { return Complex(re + c.re, im + c.im); }
    bool operator==(const Complex &c) const { return re == c.re && im == c.im; }
};

Exam angle: examiners want you to name the motivation (readability/natural syntax for user-defined types) as well as give a code example — a code-only answer loses marks.


2026, Paper I, Q9 (1 mark, MCQ) — Which operator is typically overloaded for stream insertion?

  • A) <<
  • B) >>
  • C) +
  • D) None of these

Correct answer: A. << is conventionally overloaded as the stream insertion operator to send data to an ostream such as cout. (>> is its extraction counterpart, for reading from an istream.)

📜 Past-Paper Questions & MCQs (CSS 2016–2026)

2023 · Paper-I · Q3(b) · 6 marks

Q. Write a note on the use of overloaded operators. Discuss with examples.

Ans.

  • Operator overloading gives an existing operator a new meaning for user-defined types, so objects can be used like built-in types.
  • Syntax: return_type operator<symbol>(parameters).
  • Uses: + to add complex numbers or matrices, == to compare objects, << and >> for input and output of objects, [] for array-like classes.
  • Rules: you cannot create new operators, and ::, ., .*, ?: and sizeof cannot be overloaded. Keep the meaning natural.
class Complex {
    float re, im;
public:
    Complex(float r = 0, float i = 0) : re(r), im(i) {}
    Complex operator+(const Complex &o) { return Complex(re + o.re, im + o.im); }
};
Complex c3 = c1 + c2;      // calls c1.operator+(c2)

2025 · Paper-I · Q5(a) · 10 marks

Q. Demonstrate the use of stream insertion and extraction operators for custom objects, with an example showing their functional difference.

Ans.

  • Insertion << sends data to an output stream (ostream). Extraction >> reads data from an input stream (istream) into the object.
  • They are overloaded as friend (non-member) functions, because the left operand is the stream (cout or cin), not the object. They return the stream by reference so calls can be chained (cout << a << b).
#include <iostream>
using namespace std;
class Point {
    int x, y;
public:
    friend ostream& operator<<(ostream &out, const Point &p) {   // insertion: reads the object
        out << "(" << p.x << ", " << p.y << ")";
        return out;
    }
    friend istream& operator>>(istream &in, Point &p) {          // extraction: modifies the object
        in >> p.x >> p.y;
        return in;
    }
};
int main() {
    Point p;
    cout << "Enter x y: ";
    cin >> p;            // extraction fills the object
    cout << p << endl;   // insertion prints the object
}
  • Functional difference: << only reads the object (so it takes it as const &), whereas >> changes the object (so it takes a non-const reference).

MCQs

2026 P-I · Q9. Which operator is typically overloaded for stream insertion?

  • (A) << ✅
  • (B) >>
  • (C) +
  • (D) None of these
  • Answer: (A) <<.
  • Why: cout << object inserts data into an output stream, so operator<< is overloaded for output. >> is the stream extraction operator (input), and + is for addition.