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 withcout/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
- Which operator CANNOT be overloaded in C++?
- a)
+b)==c)::(scope resolution) d)<<
- a)
- Function/operator overloading is an example of:
- a) Runtime polymorphism b) Compile-time polymorphism c) Inheritance d) Encapsulation
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
- Overloading
operator+between twoComplexobjects lets you write:- a)
add(a,b)only b)a + bdirectly, like built-in types c) Nothing different d) Only integer addition
- a)
✅ 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:
| Operator | Name | Why it cannot be overloaded |
|---|---|---|
| :: | Scope resolution | Its operands are names/scopes, not values; resolved entirely at compile time |
| . | Member access | Overloading it would make every member name ambiguous |
| .* | Pointer-to-member access | Same reason as the dot operator |
| ?: | Ternary conditional | Requires short-circuit evaluation of only one branch, which a function call cannot reproduce |
| sizeof | Size query | A compile-time property of a type, fixed by the language |
| typeid | Type identification | Built-in RTTI facility |
| alignof | Alignment query | Compile-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 byconst&(only reads it) and returnsostream&.operator>>takes the object by non-const&(it must modify it) and returnsistream&.- 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
::,.,.*,?:andsizeofcannot 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 (
coutorcin), 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 asconst &), 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 << objectinserts data into an output stream, sooperator<<is overloaded for output.>>is the stream extraction operator (input), and+is for addition.