Unit 4

Templates & Exceptions

Generic programming + robust error handling — deep internals and exam mastery

← Home
01
Function Templates
02
Class Templates
03
Specialization
04
try-catch-throw
05
Multiple Catch
06
Custom Exceptions
07
Stack Unwinding
08
Exam Questions
C++Compiler
01

Function Templates

AnalogyA cookie cutter — one mold, different dough (int, double, string). Templates let you write one function that works for any data type.

The Problem Without Templates

// Without templates — must write separate functions:
int maxInt(int a, int b)       { return a  b ? a : b; }
double maxDouble(double a, double b) { return a  b ? a : b; }
// Same logic, different types → code duplication

Function Template Syntax

template <typename T>   // T is a placeholder type
T maxVal(T a, T b) {
    return a  b ? a : b;
}

int main() {
    cout << maxVal(3, 7);          // T=int, returns 7
    cout << maxVal(3.5, 2.1);      // T=double, returns 3.5
    cout << maxVal('a', 'z');      // T=char, returns 'z'
}

How it Works Internally

The compiler generates separate versions of the function for each type used — this is called template instantiation. The template itself is not compiled — only the instantiated versions are.

// Compiler automatically generates:
int maxVal(int a, int b) { return a  b ? a : b; }
double maxVal(double a, double b) { return a  b ? a : b; }

Multiple Template Parameters

template <typename T, typename U>
void display(T a, U b) {
    cout << a << " and " << b << "\n";
}

display(42, "hello");   // T=int, U=const char*
display(3.14, 'A');   // T=double, U=char

Explicit Template Instantiation

template <typename T>
T add(T a, T b) { return a + b; }

// Explicit: tell compiler which type to use
cout << add<int>(3, 4);        // explicitly int
cout << add<double>(3, 4);     // 3 and 4 treated as double → 7.0
Memory Trick"Template = Type placeholder. Compiler is the real programmer — it writes the actual functions for you."
template <typename T> T myMax(T a, T b){ return (a > b) ? a : b; }
int main(){ cout << myMax<int>(3, 7) << myMax<char>('g', 'e'); }
► Expected Output
7g
1
Define template with template .
2
T is a placeholder for any data type.
3
Compiler generates separate functions for int, char, etc.
🔨 Build From Scratch — Templates
template <typename T> T add(T a, T b){ return a+b; }
► Expected Output
1
Generic programming.
02

Class Templates

AnalogyA Box that can hold anything — toys, books, food. The box structure is the same; only what's inside changes. Class templates let you write one class that works for any type.
template <typename T>
class Box {
    T value;
public:
    Box(T v) : value(v) {}
    T get() { return value; }
    void set(T v) { value = v; }
};

int main() {
    Box<int>    intBox(42);
    Box<double> dblBox(3.14);
    Box<string> strBox("hello");

    cout << intBox.get();  // 42
    cout << dblBox.get();  // 3.14
}
▶ Expected Output
42 3.14

Methods Defined Outside Class

template <typename T>
class Stack {
    T arr[100];
    int top = -1;
public:
    void push(T val);
    T pop();
    bool isEmpty();
};

// Must repeat template prefix outside class:
template <typename T>
void Stack<T>::push(T val) {
    arr[++top] = val;
}

template <typename T>
T Stack<T>::pop() {
    return arr[top--];
}

Class Template with Default Type

template <typename T = int>  // default type is int
class Container {
    T data;
public:
    Container(T d) : data(d) {}
    T get() { return data; }
};

Container<> c1(10);          // uses default int
Container<double> c2(3.14); // explicit double
Template vs OverloadingUse templates when logic is identical, just type differs. Use overloading when behavior differs per type.
template <class T> class Pair { public: T first, second; Pair(T a, T b){ first=a; second=b; } };
int main(){ Pair<int> p(10, 20); }
► Expected Output
1
Apply template to a whole class.
2
Must specify type in angle brackets during instantiation: Pair.
3
Useful for generic containers (Stack, List).
03

Template Specialization

AnalogyA general cookie cutter, but for star-shaped cookies you use a special star mold. Specialization = custom behavior for a specific type.

Full (Explicit) Specialization

template <typename T>
void print(T val) {
    cout << "Value: " << val << "\n";
}

// Specialization for char*
template <>  // empty <> means specialization
void print<const char*>(const char* val) {
    cout << "String: " << val << "\n";
}

print(42);          // Uses generic: "Value: 42"
print("hello");    // Uses specialization: "String: hello"

Class Template Specialization

template <typename T>
class Printer {
public:
    void print(T val) { cout << val; }
};

// Specialized for bool type
template <>
class Printer<bool> {
public:
    void print(bool val) {
        cout << (val ? "true" : "false");
    }
};

Printer<int>  p1; p1.print(42);    // 42
Printer<bool> p2; p2.print(1);    // true

Partial Specialization

template <typename T, typename U>
class Pair {
public:
    void info() { cout << "Generic Pair\n"; }
};

// Partial: specialize when both types are same
template <typename T>
class Pair<T, T> {
public:
    void info() { cout << "Same-type Pair\n"; }
};

Pair<int,double> p1; p1.info();  // Generic Pair
Pair<int,int>    p2; p2.info();  // Same-type Pair
Generic Template Full Specialization Partial Specialization
Works for All types One specific type Subset of types
Syntax template<T> template<> template<T> with partial spec
Memory Trick"Specialize = Special treatment for special types. template<> with empty brackets = fully specialized."
template <> class Pair<char> { /* special logic for char */ };
► Expected Output
1
Special implementation for a specific type.
2
Use template <> with type specified.
3
Overrides generic template for that type only.
04

Exception Handling — try, catch, throw

Analogytry = attempt a risky task. throw = signal that something went wrong (throw a red flag). catch = handle the problem. Like try to open a file, throw error if missing, catch and show message.

Basic Syntax

try {
    // code that might fail
    throw exception;   // manually throw
}
if (ExceptionType e) {
    // handle exception
}

Complete Example

int divide(int a, int b) {
    if (b == 0)
        throw string("Division by zero!");
    return a / b;
}

int main() {
    try {
        int result = divide(10, 0);
        cout << result;
    }
    catch (string e) {
        cout << "Error: " << e << "\n";
    }
    // Output: Error: Division by zero!
}
▶ Expected Output
Error: Division by zero!

Throw Different Types

throw 42;           // throw int
throw 3.14;         // throw double
throw "error msg";  // throw C-string
throw MyException(); // throw object
Flow ControlWhen throw executes: (1) rest of try block skipped, (2) matching catch found, (3) program continues after catch. If no catch matches → program terminates via std::terminate().

Re-throwing Exceptions

try {
    try {
        throw 100;
    }
    catch (int e) {
        cout << "Inner catch: " << e;
        throw;  // re-throw same exception up
    }
}
catch (int e) {
    cout << "Outer catch: " << e;
}
try { int age=15; if(age<18) throw "Underage"; } catch(const char* msg){ cout<<msg; }
► Expected Output
Underage
1
try block contains code that might throw.
2
throw keyword signals an error.
3
catch block handles the specific type thrown.
05

Multiple Catch Blocks

AnalogyMultiple safety nets — if the first net misses, the second catches. C++ tries each catch block in order until one matches.

Syntax & Rules

try {
    // risky code
}
catch (int e)    { cout << "Int exception: "    << e; }
catch (double e) { cout << "Double exception: " << e; }
catch (string e) { cout << "String exception: " << e; }
catch (...)      { cout << "Unknown exception!"; }  // catch-all
Critical Rule — Order MattersPlace specific catches BEFORE general ones. The catch-all (...) must always be LAST. Compiler processes catches top to bottom — first match wins.

Full Example with Multiple Catches

void test(int x) {
    if (x == 1) throw 10;
    if (x == 2) throw 3.14;
    if (x == 3) throw "oops";
    if (x == 4) throw 'E';
}

int main() {
    for (int i = 1; i <= 4; i++) {
        try { test(i); }
        catch(int e)    { cout << "Int: "    << e << "\n"; }
        catch(double e) { cout << "Double: " << e << "\n"; }
        catch(...)       { cout << "Other exception\n"; }
    }
    // Output: Int: 10 / Double: 3.14 / Other exception / Other exception
}
▶ Expected Output
Int: 10 / Double: 3.14 / Other exception / Other exception

catch(…) — The Universal Catcher

try {
    throw MyCustomClass();  // any type
}
catch (...) {
    // catches ANYTHING — but can't access the exception object
    cout << "Something went wrong!\n";
}
try { throw 404; } catch(int e){ cout<<"Int"; } catch(...){ cout<<"Any"; }
► Expected Output
Int
1
Multiple catch blocks for different types.
2
catch(...) is a catch-all handler (must be last).
3
Only the first matching catch is executed.
06

Custom Exception Classes

AnalogyStandard exceptions are generic error forms. Custom exceptions are specialized forms tailored to your application — like a "InsufficientFundsException" for a banking app.

Creating Custom Exceptions

// Inherit from std::exception (best practice)
class MyException : public std::exception {
    string msg;
public:
    MyException(string m) : msg(m) {}
    const char* what() const noexcept override {
        return msg.c_str();
    }
};

Using Custom Exceptions

class BankAccount {
    double balance;
public:
    BankAccount(double b) : balance(b) {}
    void withdraw(double amount) {
        if (amount  balance)
            throw MyException("Insufficient funds!");
        balance -= amount;
    }
};

int main() {
    try {
        BankAccount acc(100.0);
        acc.withdraw(200.0);
    }
    catch (const std::exception& e) {
        cout << "Exception: " << e.what() << "\n";
    }
    // Output: Exception: Insufficient funds!
}
▶ Expected Output
Exception: Insufficient funds!

Exception Hierarchy

std::exception
├── std::runtime_error
│   ├── std::overflow_error
│   └── std::underflow_error
├── std::logic_error
│   ├── std::invalid_argument
│   └── std::out_of_range
└── std::bad_alloc  (new fails)
Best PracticeAlways inherit from std::exception and override what(). This allows catching via catch(const std::exception& e) — catches all standard and custom exceptions.
class MyEx : public exception { public: const char* what() const throw(){ return "Custom Error"; } };
► Expected Output
1
Inherit from std::exception.
2
Override what() method for error message.
3
Throw instance: throw MyEx();
07

Stack Unwinding & Exception Safety

AnalogyWhen a fire alarm goes off in a building, people leave in reverse order they entered. Stack unwinding = destructors called in reverse order when exception propagates up the call stack.

What is Stack Unwinding?

When an exception is thrown, C++ automatically destroys all local objects (calls their destructors) as it exits each scope looking for a matching catch block. This is stack unwinding.

class Guard {
    string name;
public:
    Guard(string n) : name(n) { cout << name << " created\n"; }
    ~Guard() { cout << name << " destroyed\n"; }  // called during unwinding!
};

void risky() {
    Guard g1("G1");
    Guard g2("G2");
    throw string(42);  // exception here
    Guard g3("G3");  // never created
}

int main() {
    try { risky(); }
    catch(int e) { cout << "Caught: " << e; }
}
// Output:
// G1 created
// G2 created
// G2 destroyed  ← unwinding, reverse order
// G1 destroyed
// Caught: 42
▶ Expected Output
called during unwinding!

Exception Safety Levels

Level Guarantee
No-throw Function never throws. Mark with noexcept
Strong If fails, state rolls back (commit-or-rollback)
Basic If fails, no leaks, state is valid (but changed)
None No guarantees — avoid this
void safeFunc() noexcept {  // promises never to throw
    // if exception occurs here, std::terminate() called
}
RAII PatternResource Acquisition Is Initialization — acquire resources in constructor, release in destructor. Stack unwinding guarantees destructors run, preventing leaks. This is WHY smart pointers (unique_ptr, shared_ptr) are safe.
Memory Trick — Stack Unwinding"When exception travels UP the stack, destructors travel DOWN — reverse creation order."
void func(){ Test t; throw 1; } // t destroyed before catch
► Expected Output
1
When exception occurs, local objects are destroyed.
2
Destructors are called in reverse order of creation.
3
Ensures resource cleanup even during errors.
08

Exam Questions

2-Mark Questions

Q1. What is a function template? Give syntax.
A function template is a blueprint for creating functions that work with any data type. Syntax: template <typename T> T func(T a) { ... }. Compiler generates actual functions via template instantiation.
Q2. What is template specialization?
Providing a specific implementation for a particular type. Full: template<> void func<int>(int x){...}. Partial: specializing for a subset of type combinations.
Q3. What are try, catch, throw?
try: block where exception might occur. throw: signals an exception. catch: handles the exception. Flow: exception thrown → try block exits → matching catch found → execution continues after catch.
Q4. What is stack unwinding?
When exception propagates up the call stack, C++ automatically calls destructors of all local objects in each scope in reverse order of creation. Ensures no resource leaks.

5-Mark Questions

Q5. Write a class template for a Stack with push and pop operations.
Use template <typename T> class Stack with array member T arr[100], int top=-1. push(): arr[++top]=val. pop(): return arr[top--]. isEmpty(): return top==-1. Each method outside needs template<typename T> void Stack<T>::push(T val).
Q6. Explain multiple exception handling with example. What is catch-all?
Multiple catch blocks handle different exception types. Order matters — specific before general. catch(...) is catch-all that matches any exception type but can't access the exception object. Must be last catch block.
Q7. How do you create a custom exception class? Why inherit from std::exception?
Inherit from std::exception, override what() returning const char*. Inheriting allows catching custom exceptions via catch(const std::exception& e) — a unified catch for all standard+custom exceptions. Override what() to return error message.

Revision Sheet

Unit 4 Key Points

template <typename T> — T is type placeholder
Instantiation — compiler generates type-specific functions automatically
template<> — empty brackets = full specialization
try-throw-catch — throw exits try, catch handles it
catch(...) — catch all, must be last
Custom exception — inherit std::exception, override what()
Stack unwinding — destructors run in reverse when exception propagates
noexcept — promises function never throws


💻

Complete Program — Function Templates

Function Templates — Swap, Max, Sort (Fully Compilable)
#include <iostream>
#include <string>
using namespace std;

// Generic swap
template <typename T>
void mySwap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

// Generic max
template <typename T>
T maxOf(T a, T b) {
    return a > b ? a : b;
}

// Two-type display
template <typename T, typename U>
void display(T a, U b) {
    cout << a << " and " << b << "\n";
}

// Explicit specialization for bool max
template <>
bool maxOf<bool>(bool a, bool b) {
    return a || b;   // custom logic for bool
}

int main() {
    // swap ints
    int x = 10, y = 20;
    mySwap(x, y);
    cout << "x=" << x << " y=" << y << "\n";

    // swap strings
    string s1 = "hello", s2 = "world";
    mySwap(s1, s2);
    cout << s1 << " " << s2 << "\n";

    // maxOf
    cout << maxOf(3, 7) << "\n";
    cout << maxOf(3.14, 2.71) << "\n";
    cout << maxOf('a', 'z') << "\n";

    // two-type display
    display(42, "hello");
    display(3.14, 'A');

    return 0;
}
▶ Expected Output
🧠 Deep Code Explanation
1
template <typename T> void mySwap(T& a, T& b) — T is a placeholder. When called with ints, compiler generates void mySwap(int&, int&). When called with string, it generates a separate string version. Two instantiations exist in the binary.
2
T temp = a; a = b; b = temp; — classic 3-step swap. Works for ANY type that supports copy assignment (operator=). int, double, string all support it.
3
template <typename T, typename U> void display(T a, U b) — TWO independent type params. Calling display(42, "hello"): compiler deduces T=int, U=const char*. These can be different types.
4
template<> bool maxOf<bool>(...) — full specialization. Empty <> after template keyword means "no generic params — this is a specific version for bool". The compiler uses this version ONLY when T=bool is deduced.
5
mySwap(s1, s2): s1="hello", s2="world". temp="hello", s1="world", s2="hello". Output: "world hello". Strings are swapped correctly by value using std::string's copy assignment.
✍️ How to Write This in Exam
1
Write #include <iostream> and using namespace std; first. For string operations also add #include <string>.
2
Template goes on the line IMMEDIATELY before the function. template <typename T> then next line the function. No blank line between them.
3
For specialization: write template<> (empty brackets), then function with explicit type in angle brackets: bool maxOf<bool>(bool a, bool b).
4
Common mistake: writing template<T> without the typename keyword. Always: template<typename T> or template<class T>.
▶ Dry Run — mySwap(x, y) where x=10, y=20
Call: mySwap(x,y)T deduced as int. Function becomes: void mySwap(int& a, int& b). a=x (alias), b=y (alias)
T temp = atemp = 10. Stack: temp=10, a(x)=10, b(y)=20
a = bx = 20. Stack: temp=10, a(x)=20, b(y)=20
b = tempy = 10. Stack: temp=10, a(x)=20, b(y)=10
Return to mainx=20, y=10. Output: "x=20 y=10" ✓
💻

Complete Program — Class Templates

Class Template — Generic Box + Stack (Fully Compilable)
#include <iostream>
#include <string>
#include <stdexcept>
using namespace std;

template <typename T>
class Stack {
    T arr[50];
    int top;
public:
    Stack() : top(-1) {}

    void push(T val) {
        if (top == 49) throw overflow_error("Stack is full");
        arr[++top] = val;
    }

    T pop() {
        if (top == -1) throw underflow_error("Stack is empty");
        return arr[top--];
    }

    T peek() const {
        if (top == -1) throw underflow_error("Stack is empty");
        return arr[top];
    }

    bool isEmpty() const { return top == -1; }

    void display() const {
        cout << "Stack (top->bottom): ";
        for (int i = top; i >= 0; i--)
            cout << arr[i] << " ";
        cout << "\n";
    }
};

int main() {
    // Integer stack
    Stack<int> si;
    si.push(10); si.push(20); si.push(30);
    si.display();                   // Stack (top-bottom): 30 20 10
    cout << "Pop: " << si.pop() << "\n"; // Pop: 30
    cout << "Peek: " << si.peek() << "\n"; // Peek: 20

    // String stack
    Stack<string> ss;
    ss.push("alpha"); ss.push("beta");
    ss.display();                   // Stack (top-bottom): beta alpha

    // Exception test
    try {
        Stack<int> empty;
        empty.pop();                // throws underflow_error
    } catch (const underflow_error& e) {
        cout << "Caught: " << e.what() << "\n";
    }

    return 0;
}
▶ Expected Output
🧠 Deep Code Explanation
1
template <typename T> class Stack — one class, works for any type. T arr[50]: if T=int, this is int[50]; if T=string, this is string[50]. Compiler generates two complete separate classes.
2
Stack() : top(-1) {} — initializer list sets top to -1, meaning "empty". index 0..49 are valid positions. top tracks the last used index.
3
push(10): top=-1, ++top=0, arr[0]=10. push(20): top=0, ++top=1, arr[1]=20. push(30): top=1, ++top=2, arr[2]=30. display loops from top=2 down to 0: prints 30, 20, 10.
4
pop(): top=2, val=arr[2]=30, top--=1. Returns 30. peek(): top=1, returns arr[1]=20 WITHOUT decrementing top.
5
empty.pop(): top==-1 → throw underflow_error("Stack is empty"). Exception propagates to catch block. e.what() returns "Stack is empty".
✍️ How to Write This in Exam
1
Write template <typename T> on the line before class Stack {. Inside class, replace every concrete type with T.
2
If methods are defined OUTSIDE the class: prefix each with template <typename T> and use Stack<T>:: scope.
3
Instantiate with: Stack<int> s — type must be explicit in angle brackets (unlike function templates where compiler deduces).
4
Default value T{} — use it for uninitialized T returns. For int T{} = 0. For string T{} = "".
▶ Dry Run — push sequence on Stack<int>
Initial statetop=-1, arr[50] uninitialized. isEmpty()=true
push(10)top: -1→0. arr[0]=10
push(20)top: 0→1. arr[1]=20
push(30)top: 1→2. arr[2]=30
display()Loop i=2→0: print arr[2]=30, arr[1]=20, arr[0]=10. Output: "30 20 10"
pop()val=arr[2]=30, top: 2→1. Returns 30. arr[2] logically gone.
peek()top=1, returns arr[1]=20. top unchanged=1.
💻

Complete Program — Exception Handling + Custom Exception

Custom Exception + Multi-Catch + Stack Unwinding (Fully Compilable)
#include <iostream>
#include <string>
#include <stdexcept>
using namespace std;

// Custom exception class
class InsufficientFundsException : public exception {
    string msg;
public:
    InsufficientFundsException(double needed, double have) {
        msg = "Need " + to_string(needed) + " but have " + to_string(have);
    }
    const char* what() const noexcept override {
        return msg.c_str();
    }
};

// RAII guard for demo
class TxGuard {
    string name;
    bool committed = false;
public:
    TxGuard(string n) : name(n) {
        cout << "[TX] Start: " << name << "\n";
    }
    void commit() { committed = true; }
    ~TxGuard() {
        if (!committed)
            cout << "[TX] Rollback: " << name << "\n";
        else
            cout << "[TX] Commit: " << name << "\n";
    }
};

class BankAccount {
    double balance;
public:
    BankAccount(double b) : balance(b) {}

    void deposit(double amt) {
        if (amt <= 0) throw invalid_argument("Amount must be positive");
        balance += amt;
        cout << "Deposited: " << amt << ", Balance: " << balance << "\n";
    }

    void withdraw(double amt) {
        TxGuard tx("withdraw");        // RAII — destructor auto-called
        if (amt > balance)
            throw InsufficientFundsException(amt, balance);
        balance -= amt;
        tx.commit();
        cout << "Withdrew: " << amt << ", Balance: " << balance << "\n";
    }

    double getBalance() const { return balance; }
};

int main() {
    BankAccount acc(5000);

    // Test 1: Valid withdraw
    cout << "--- Test 1 ---\n";
    try {
        acc.withdraw(1000);
    } catch (...) { cout << "Error\n"; }

    // Test 2: Insufficient funds
    cout << "--- Test 2 ---\n";
    try {
        acc.withdraw(9999);
    }
    catch (const InsufficientFundsException& e) {
        cout << "Bank Error: " << e.what() << "\n";
    }
    catch (const invalid_argument& e) {
        cout << "Input Error: " << e.what() << "\n";
    }
    catch (...) {
        cout << "Unknown error\n";
    }

    // Test 3: Invalid deposit
    cout << "--- Test 3 ---\n";
    try {
        acc.deposit(-500);
    }
    catch (const InsufficientFundsException& e) { cout << "Bank: " << e.what(); }
    catch (const invalid_argument& e) { cout << "Invalid: " << e.what() << "\n"; }

    return 0;
}
▶ Expected Output
🧠 Deep Code Explanation
1
class InsufficientFundsException : public exception — inherits from std::exception. This allows catching via catch(const exception& e) as a unified net. override what() returns the stored message.
2
TxGuard tx("withdraw") — RAII object. Constructor prints "Start". If exception occurs before tx.commit(), destructor fires during stack unwinding and prints "Rollback".
3
Test 2: withdraw(9999). amt=9999 > balance=4000 → throw InsufficientFundsException. Stack unwinds: tx destructor runs BEFORE catch block executes → prints "Rollback". Then catch(InsufficientFundsException) prints the error.
4
catch ORDER matters: InsufficientFundsException BEFORE invalid_argument BEFORE catch(...). If base exception was first, derived would never be caught.
5
const char* what() const noexcept override — const means "doesn't modify object". noexcept means "what() itself never throws". override ensures we're overriding the base's virtual what().
✍️ How to Write This in Exam
1
Custom exception template: class MyEx : public exception { string msg; public: MyEx(string m):msg(m){} const char* what() const noexcept override { return msg.c_str(); } };
2
Identify ALL failure points in the function first. Add throw at each. Then structure try-catch in main with specific → general order.
3
Always catch by const reference: if(const MyEx& e). Never by value — avoids slicing and copy overhead.
4
Include #include <stdexcept> for standard exceptions (invalid_argument, out_of_range, overflow_error, underflow_error, runtime_error).
▶ Dry Run — withdraw(9999) with balance=4000
Enter withdraw(9999)TxGuard tx created → prints "[TX] Start: withdraw"
if(9999 > 4000)Condition TRUE → execute throw InsufficientFundsException(9999, 4000)
Exception thrownwithdraw() exits immediately. tx goes out of scope → ~TxGuard() called → committed=false → prints "[TX] Rollback: withdraw"
Stack unwinds to maincatch(InsufficientFundsException& e) matches → e.what() returns the message string
Output"Bank Error: Need 9999.000000 but have 4000.000000"

P1

Practice — Function Templates

✍️ Write From Scratch
Basic
Write a function template swapValues(T& a, T& b) that swaps two values. Test with int, double, and string. DO NOT use std::swap.
Show Solution ▾
template <typename T>
void swapValues(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}
int main() {
    int x=5, y=10;
    swapValues(x, y);
    cout << x << " " << y << "\n"; // 10 5
    double p=1.5, q=2.7;
    swapValues(p, q);
    string s1="hello", s2="world";
    swapValues(s1, s2);
    cout << s1; // world
}
▶ Expected Output
10 5 world
Medium
Write a template function findMax(T arr[], int n) that finds the maximum element in an array of any type. Test with int[], double[], and char[].
Show Solution ▾
template <typename T>
T findMax(T arr[], int n) {
    T maxVal = arr[0];
    for(int i=1; i<n; i++)
        if(arr[i] > maxVal) maxVal = arr[i];
    return maxVal;
}
int main() {
    int a[] = {3,1,9,2,7};
    cout << findMax(a, 5);  // 9
    char c[] = {'a','z','m'};
    cout << findMax(c, 3);  // z
}
▶ Expected Output
9 z
Tricky
Write a template function countOccurrences(T arr[], int n, T target) that counts how many times target appears. Then write a template function printReverse(T arr[], int n) that prints array in reverse. Combine both: count + print reverse in one call.
Show Solution ▾
template <typename T>
int countOccurrences(T arr[], int n, T target) {
    int count = 0;
    for(int i=0; i<n; i++)
        if(arr[i] == target) count++;
    return count;
}
template <typename T>
void printReverse(T arr[], int n) {
    for(int i=n-1; i>=0; i--) cout << arr[i] << " ";
    cout << "\n";
}
int main() {
    int a[] = {1,2,3,2,2};
    cout << countOccurrences(a,5,2); // 3
    printReverse(a,5);              // 2 2 3 2 1
}
▶ Expected Output
3
🧠 How to Approach Templates in Exam
1
Identify the type-independent logic — if the same algorithm works for int, double, string → template it. If behavior differs per type → use overloading or specialization.
2
Write template syntax first: template <typename T> on the line BEFORE the function. Replace all type names with T.
3
Think about what operations T must support — if you do a > b, T must have operator>. If you do a + b, T must have operator+. Mention this in viva!
4
For multiple types: template <typename T, typename U>. For return type that differs: use auto or a third template param.
5
When to use template vs overloading: Same logic, different types = template. Different logic, same or different types = overloading.
🔒 Memory Lock — Template Pattern "template <typename T> → replace the type with T → done. The compiler writes the actual functions for you at compile time."
🧪 Mini Practice — Templates
1. What keyword introduces a template? What are two alternative forms?
template <typename T> — also valid: template <class T>. Both are identical. typename is preferred in modern C++.
2. If you call add(3, 4.5) with template T add(T a, T b) — will it compile?
NO — type deduction fails. 3 is int, 4.5 is double — T can't be both. Fix: add<double>(3, 4.5) explicitly, or use two template params: template<T,U>.
3. When is a template function actually compiled?
Only when instantiated — when you call it with a specific type. The template itself is not compiled, only the instantiated versions are.
4. Write in one line: a template function signature that returns the larger of two values of same type.
template<typename T> T maxOf(T a, T b) { return a > b ? a : b; }
P2

Practice — Class Templates

✍️ Write From Scratch — Class Templates
Basic
Write a class template Pair<T, U> that stores two values of potentially different types. Include: constructor, getFirst(), getSecond(), display(). Test with (int, string) and (double, char).
Show Solution ▾
template <typename T, typename U>
class Pair {
    T first; U second;
public:
    Pair(T f, U s) : first(f), second(s) {}
    T getFirst()  { return first; }
    U getSecond() { return second; }
    void display() { cout << "(" << first << ", " << second << ")\n"; }
};
int main() {
    Pair<int, string> p1(1, "hello");
    Pair<double, char> p2(3.14, 'A');
    p1.display(); // (1, hello)
    p2.display(); // (3.14, A)
}
Medium
Write a class template Stack<T> with array-based storage (max 50 elements). Implement: push(T), pop() returns T, peek() returns top without removing, isEmpty(), isFull(), display(). Test with Stack<int> and Stack<string>.
Show Solution ▾
template <typename T>
class Stack {
    T arr[50];
    int top;
public:
    Stack() : top(-1) {}
    bool isEmpty() { return top == -1; }
    bool isFull()  { return top == 49; }
    void push(T val) {
        if(isFull()) { cout << "Stack full!\n"; return; }
        arr[++top] = val;
    }
    T pop() {
        if(isEmpty()) { cout << "Stack empty!\n"; return T{}; }
        return arr[top--];
    }
    T peek() { return arr[top]; }
    void display() {
        for(int i=top; i>=0; i--) cout << arr[i] << " ";
        cout << "\n";
    }
};
int main() {
    Stack<int> s;
    s.push(1); s.push(2); s.push(3);
    s.display();       // 3 2 1
    cout << s.pop();   // 3
    cout << s.peek();  // 2
}
▶ Expected Output
3 2
Tricky
Write a class template MinMax<T> that tracks the minimum and maximum of all values added to it. Methods: add(T val), getMin(), getMax(), getRange() = max - min, reset(). Must work for int, double, and char. What happens if no values added and getMin() is called? Handle it properly.
Show Solution ▾
template <typename T>
class MinMax {
    T minVal, maxVal;
    bool hasData;
public:
    MinMax() : hasData(false) {}
    void add(T val) {
        if(!hasData) { minVal = maxVal = val; hasData = true; }
        else {
            if(val < minVal) minVal = val;
            if(val > maxVal) maxVal = val;
        }
    }
    T getMin() {
        if(!hasData) throw runtime_error("No data added!");
        return minVal;
    }
    T getMax() {
        if(!hasData) throw runtime_error("No data added!");
        return maxVal;
    }
    void reset() { hasData = false; }
};
int main() {
    try {
        MinMax<int> mm;
        mm.add(5); mm.add(2); mm.add(9);
        cout << mm.getMin(); // 2
        cout << mm.getMax(); // 9
        MinMax<int> empty;
        empty.getMin(); // throws!
    } catch(runtime_error& e) { cout << e.what(); }
}
▶ Expected Output
2 9
🧠 How to Design a Class Template
1
Write the regular class first for one type (e.g., int). Get it working. Then replace int with T and add template<typename T> before the class.
2
For methods outside the class: Every method needs its own template<typename T> prefix AND use ClassName<T>:: instead of just ClassName::.
3
Default value for T: Use T{} — this zero-initializes for int (gives 0), empty-initializes for string (gives ""), etc.
4
Instantiation syntax: Stack<int>, Stack<string> — always specify type in angle brackets for class templates (unlike function templates where compiler deduces).
🔒 Memory Lock — Class Template Key Rule "Methods defined OUTSIDE class need TWO things: template<typename T> prefix + ClassName<T>:: scope. Miss either one → compile error."
P3

Practice — Exception Handling

✍️ Write From Scratch — Exceptions
Basic
Write a function safeDivide(int a, int b) that throws a string exception "Division by zero" if b==0. In main, call it inside try-catch. Test with b=0 and b=5.
Show Solution ▾
double safeDivide(int a, int b) {
    if(b == 0) throw string("Division by zero!");
    return (double)a / b;
}
int main() {
    try {
        cout << safeDivide(10, 2) << "\n"; // 5
        cout << safeDivide(10, 0) << "\n"; // throws
    }
    catch(string e) {
        cout << "Error: " << e << "\n";
    }
}
▶ Expected Output
5
Medium — Custom Exception + Custom Class
Create custom exception class BankException (inherits std::exception, stores message). Class BankAccount with private balance. deposit(amount): throws if amount<=0. withdraw(amount): throws BankException if insufficient funds. transfer(BankAccount&, amount): throws if same problems. Test with try-multiple-catch.
Show Solution ▾
class BankException : public std::exception {
    string msg;
public:
    BankException(string m) : msg(m) {}
    const char* what() const noexcept override { return msg.c_str(); }
};

class BankAccount {
    double balance;
public:
    BankAccount(double b) : balance(b) {}
    void deposit(double amt) {
        if(amt <= 0) throw invalid_argument("Deposit must be positive");
        balance += amt;
    }
    void withdraw(double amt) {
        if(amt > balance) throw BankException("Insufficient funds!");
        balance -= amt;
    }
    void transfer(BankAccount& to, double amt) {
        withdraw(amt);  // may throw
        to.deposit(amt);
    }
    double getBalance() { return balance; }
};

int main() {
    BankAccount a(5000), b(1000);
    try {
        a.transfer(b, 3000);  // OK
        a.transfer(b, 9999);  // throws BankException
    }
    catch(const BankException& e) { cout << "Bank error: "   << e.what(); }
    catch(const invalid_argument& e) { cout << "Input error: " << e.what(); }
    catch(...) { cout << "Unknown error"; }
}
Tricky — Nested try + Rethrow
Write a function processFile(string filename) that: (1) throws int 404 if file not found, (2) throws string "corrupt" if file corrupt. In main, use NESTED try-catch: inner catches int exception (logs it, rethrows). Outer catches both int and string. Trace the exact flow.
Show Solution ▾
void processFile(string filename) {
    if(filename == "missing") throw 404;
    if(filename == "bad")     throw string("corrupt");
    cout << "File OK\n";
}
int main() {
    try {                          // outer try
        try {                      // inner try
            processFile("missing");
        }
        catch(int code) {
            cout << "Inner: HTTP " << code << "\n";
            throw;             // rethrow same exception
        }
    }
    catch(int code)    { cout << "Outer: code "  << code; }
    catch(string msg)  { cout << "Outer: msg "   << msg; }
    // Output: Inner: HTTP 404   Outer: code 404
}
▶ Expected Output
Inner: HTTP 404 Outer: code 404
🧠 How to Approach Exception Programs in Exam
1
Identify failure points first: division by zero, array out of bounds, invalid input, file not found, insufficient funds — these are throw sites.
2
Design exception class when needed: inherit std::exception, constructor takes string message, override what() returning c_str().
3
Order catch blocks: derived exception before base exception. Specific before general. catch(...) always last.
4
For rethrow: use bare throw; inside catch block — rethrows the original exception unchanged.
5
Always catch by reference: catch(const MyException& e) — prevents slicing, efficient, correct polymorphism.
🔒 Memory Lock — Exception Flow "throw → EXIT try block immediately → scan catches top-to-bottom → first match wins → if no match → terminate(). Bare throw; = same exception travels further up."
P4

Predict Output & Debug — Templates + Exceptions

▶ Predict the Output
Q1 — Template Instantiation Order
template<typename T>
T add(T a, T b) {
    cout << "add called\n";
    return a + b;
}
int main() {
    cout << add(3, 4)       << "\n";
    cout << add(1.5, 2.5)  << "\n";
    cout << add(3, 4)       << "\n";
}
Reveal ▾ Output:
add called7add called4add called7

Each call prints "add called" then result. Two instantiations (int and double) but all 3 calls execute at runtime — the "add called" prints every time the function runs, not just when compiled.
Q2 — Exception Flow with Multiple Catch
void test(int x) {
    if(x == 1) throw 10;
    if(x == 2) throw 2.5;
    if(x == 3) throw 'E';
    cout << "No exception\n";
}
int main() {
    for(int i=1; i<=4; i++) {
        try { test(i); }
        catch(int e)    { cout << "Int:"    << e << "\n"; }
        catch(double e) { cout << "Double:" << e << "\n"; }
        catch(...)       { cout << "Other\n"; }
    }
}
Reveal ▾ Output:
Int:10Double:2.5OtherNo exception

i=1: throws int 10 → catches int. i=2: throws double 2.5 → catches double. i=3: throws char 'E' → no char catch → catch(...) = "Other". i=4: no throw → prints "No exception". Note: catch(...) catches char!
Q3 — Template Specialization vs Generic
template<typename T>
void show(T val) { cout << "Generic: " << val << "\n"; }

template<>
void show<int>(int val) { cout << "Int: " << val*2 << "\n"; }

template<>
void show<bool>(bool val) { cout << (val?"YES":"NO") << "\n"; }

int main() {
    show(5);
    show(3.14);
    show(true);
    show('A');
}
Reveal ▾ Output:
Int: 10Generic: 3.14YESGeneric: A

5 is int → specialized version → 5*2=10. 3.14 is double → generic. true is bool → specialized → "YES". 'A' is char → generic.
Q4 — Stack Unwinding Proof
struct Guard {
    string name;
    Guard(string n):name(n){cout << name << " ON\n";}
    ~Guard(){cout << name << " OFF\n";}
};
void inner() {
    Guard g("G2");
    throw 42;
}
void outer() {
    Guard g("G1");
    inner();
}
int main() {
    try { outer(); }
    catch(int e){ cout << "Caught: " << e << "\n"; }
}
Reveal ▾ Output:
G1 ONG2 ONG2 OFFG1 OFFCaught: 42

G1 created in outer(). G2 created in inner(). throw 42 → G2 destroyed (inner scope unwinds) → G1 destroyed (outer scope unwinds) → catch in main. Destructors run even during exception propagation — this is RAII!
🐞 Debug These — Templates & Exceptions
Bug 1 — Template Syntax Error
template <T>             // Line A
T multiply(T a, T b) {
    return a * b;
}
int main() {
    cout << multiply(3, 4);
    cout << multiply(2.0, 1.5);
    cout << multiply(3, 4.0); // Line B
}
▶ Expected Output
Line B
Show Fix ▾ Bug 1 — Line A: Missing typename or class. Must be: template <typename T>

Bug 2 — Line B: Type deduction conflict. 3 is int, 4.0 is double. T cannot be both simultaneously.

Fix:
• Line A: template <typename T>
• Line B: multiply<double>(3, 4.0) or use two params: template<typename T, typename U>
Bug 2 — Wrong Catch Order
class Base : public std::exception {};
class Derived : public Base {};

int main() {
    try { throw Derived(); }
    if(Base& e)    { cout << "Base caught\n"; }    // A
    if(Derived& e) { cout << "Derived caught\n"; } // B — UNREACHABLE!
    catch(...)         { cout << "All caught\n"; }
}
▶ Expected Output
A B — UNREACHABLE!
Show Fix ▾ Problem: catch(Base&) comes before catch(Derived&). Since Derived IS-A Base, the Base catch always matches first. The Derived catch is UNREACHABLE — compiler may warn "catch block will never be executed".

Rule: ALWAYS put DERIVED exception class catches BEFORE base class catches.

Fix:
if(Derived& e) { ... } // Derived FIRST
if(Base& e) { ... } // Base after
Bug 3 — Specialization Syntax Error
template<typename T>
T square(T x) { return x * x; }

template<typename>        // WRONG
string square(string s) { return s + s; }

int main() {
    cout << square(5);
    cout << square(if("Hi"));
}
Show Fix ▾ Problem: Specialization syntax is wrong. Must use empty template<> and specify the type in the function name angle brackets.

Fix:
template<>
string square<string>(string s) { return s + s; }
Bug 4 — Exception Not Caught
int main() {
    try {
        throw 3.14;  // throws double
    }
    catch(int e)    { cout << "int\n"; }
    catch(float e)  { cout << "float\n"; }
    // No double catch, no catch(...)
}
Show Fix ▾ Problem: 3.14 is a double literal. Neither int nor float catch matches double. No catch-all. Result: std::terminate() is called — program crashes!

Fix: Add catch(double e) or catch(...). Note: float and double are different types — 3.14f is float, 3.14 is double.
P5

Edge Cases, STL Connection & Active Recall

⚠️ Edge Cases — Must Know

1. Template with Pointers — Dangerous!

template<typename T>
T getMax(T a, T b) { return a > b ? a : b; }

int main() {
    int* p1 = new int(10);
    int* p2 = new int(20);
    cout << *getMax(p1, p2); // WRONG! Compares addresses not values
    // Correct: cout << getMax(*p1, *p2);
}
▶ Expected Output
WRONG! Compares addresses not values
Pointer TrapWhen T=pointer type, operator> compares memory addresses, NOT pointed values! Always dereference before passing to template, or provide a specialization for pointer types.

2. catch(...) Misuse

try {
    throw MyException("critical error");
}
catch(...) {
    cout << "something went wrong";  // can't access exception object!
    // You lose ALL information about what went wrong
    throw;  // best practice: rethrow if you can't handle it
}
Best Practice: Use catch(...) ONLY as a last resort safety net. Always rethrow with bare throw; if you can't actually handle the exception. Never swallow exceptions silently.

3. Exception in Constructor

class Resource {
    int* data;
public:
    Resource(int size) {
        if(size <= 0) throw invalid_argument("Size must be positive");
        data = new int[size];  // may throw std::bad_alloc
    }
    ~Resource() { delete[] data; }
};
// If constructor throws: destructor NOT called! (object never fully constructed)
// data that WAS allocated before throw → memory leak
// Solution: use RAII / smart pointers inside constructor
Critical: If constructor throws an exception, the destructor for THAT class is NOT called (object wasn't fully created). Any resources allocated before the throw must be manually cleaned up — or use smart pointers to auto-manage.

4. noexcept Violation

void safe() noexcept {
    throw 42;  // NEVER do this! Promises not to throw, but does
    // Result: std::terminate() called immediately!
}
P6

Active Recall — Test Yourself (No Notes)

⏱ Close Your Notes — Answer These
1. Write the exact syntax for a function template with two different type parameters T and U.
template<typename T, typename U> void func(T a, U b) { ... }
2. What is template specialization? Write the empty brackets syntax.
Providing a specific implementation for one particular type. Syntax: template<> void func<int>(int x) { ... } — empty angle brackets after template keyword.
3. A method of a class template defined OUTSIDE the class — what two things must you write before the method?
1. template<typename T> prefix. 2. ClassName<T>:: scope operator. Both are required.
4. What happens when no catch block matches a thrown exception?
std::terminate() is called, which by default calls std::abort(), terminating the program. No cleanup of remaining stack frames guaranteed.
5. What is the difference between throw; and throw e; inside a catch block?
throw; rethrows the ORIGINAL exception unchanged (preserves type). throw e; creates a NEW exception from e — if e is a reference to a derived class, only the base part is thrown (slicing possible).
6. Why should you catch exceptions by reference (const E& e) instead of by value?
By value causes: (1) object copying overhead, (2) object slicing if derived exception caught as base type. By reference: no copy, no slicing, correct virtual dispatch for what().
7. What does noexcept do? What happens if a noexcept function throws?
noexcept promises the function will not throw. If it does throw anyway, std::terminate() is called immediately — exception does NOT propagate.
8. Write a custom exception class in 4 lines of code.
class MyEx : public std::exception {  string msg; public: MyEx(string m):msg(m){}  const char* what() const noexcept override {return msg.c_str();}};
9. What is stack unwinding? In what order?
When exception propagates, destructors of all local objects in scope are called in reverse order of creation (LIFO). This guarantees cleanup even during exceptions — the basis of RAII.
10. Template vs overloading — when to use each?
Template: same logic, different types — let compiler generate. Overloading: different logic per type — write separately. If both needed: write generic template + specialized version for special types.

🎯

Final Exam Coding Programs — Unit 4

Attempt each program from scratch before revealing. Full marks: correct template syntax, proper exception classes, correct catch order, custom what().

Program 01
Generic Sort + Search Templates
8 marks
Write template bubbleSort(T arr[], int n) and linearSearch(T arr[], int n, T key) returning index or -1. Test with int[] and string[].
Show Solution ▾
template<typename T>
void bubbleSort(T arr[], int n) {
    for(int i=0;i<n-1;i++)
        for(int j=0;j<n-1-i;j++)
            if(arr[j]>arr[j+1]) swap(arr[j],arr[j+1]);
}
template<typename T>
int linearSearch(T arr[], int n, T key) {
    for(int i=0;i<n;i++) if(arr[i]==key) return i;
    return -1;
}
int main() {
    int a[]={5,2,8,1,9}; bubbleSort(a,5);
    if(int x:a) cout<<x<<" ";  // 1 2 5 8 9
    cout<<linearSearch(a,5,8);   // 3
    string s[]={"alpha","beta","gamma"};
    bubbleSort(s,3);
    cout<<linearSearch(s,3,if("beta")); // 1
}
▶ Expected Output
1 2 5 8 9 3 1
Program 02
Template Queue Class
10 marks
Class template Queue<T> (array-based, circular, max 100). Implement: enqueue(T), dequeue() returns T, front(), back(), isEmpty(), isFull(), display(). Test with int and string.
Show Solution ▾
template<typename T>
class Queue {
    T arr[100]; int head, tail, count;
public:
    Queue():head(0),tail(0),count(0){}
    bool isEmpty(){return count==0;}
    bool isFull(){return count==100;}
    void enqueue(T val){
        if(isFull()){cout<<"Full!\n";return;}
        arr[tail]=val; tail=(tail+1)%100; count++;
    }
    T dequeue(){
        if(isEmpty()){cout<<"Empty!\n";return T{};}
        T v=arr[head]; head=(head+1)%100; count--;
        return v;
    }
    T front(){return arr[head];}
    void display(){
        int i=head;
        for(int c=0;c<count;c++){cout<<arr[i]<<" ";i=(i+1)%100;}
        cout<<"\n";
    }
};
int main(){
    Queue<int> q;
    q.enqueue(1);q.enqueue(2);q.enqueue(3);
    q.display();        // 1 2 3
    cout<<q.dequeue();  // 1
    cout<<q.front();    // 2
}
▶ Expected Output
1 2
Program 03
Template Specialization — Type-Specific Display
7 marks
Generic display(T val) prints "Value: val". Full specialization for const char* prints length too. Full specialization for bool prints YES/NO. Test all.
Show Solution ▾
template<typename T>
void display(T val){ cout<<"Value: "<<val<<"\n"; }

template<>
void display<const char*>(const char* val){
    cout<<"String: "<<val<<" (len="<<strlen(val)<<")\n";
}
template<>
void display<bool>(bool val){
    cout<<"Boolean: "<<(val?"YES":"NO")<<"\n";
}
int main(){
    display(42);          // Value: 42
    display(3.14);        // Value: 3.14
    display("Hello");     // String: Hello (len=5)
    display(true);        // Boolean: YES
}
Program 04
Custom Exception Hierarchy
10 marks
AppException (base, message). DatabaseException:AppException (adds query). NetworkException:AppException (adds errorCode). Function throws one based on input. Catch derived before base.
Show Solution ▾
class AppException : public std::exception {
protected: string msg;
public:
    AppException(string m):msg(m){}
    const char* what() const noexcept override {return msg.c_str();}
};
class DatabaseException : public AppException {
    string query;
public:
    DatabaseException(string m,string q):AppException(m),query(q){}
    const char* what() const noexcept override {
        static string r; r=msg+" [Query: "+query+"]"; return r.c_str();
    }
};
class NetworkException : public AppException {
    int code;
public:
    NetworkException(string m,int c):AppException(m),code(c){}
    const char* what() const noexcept override {
        static string r; r=msg+" [Code: "+to_string(code)+"]"; return r.c_str();
    }
};
void simulate(int x){
    if(x==1) throw DatabaseException("DB fail","SELECT *");
    if(x==2) throw NetworkException("Timeout",504);
    if(x==3) throw AppException("Generic error");
    cout<<"OK\n";
}
int main(){
    for(int i=1;i<=4;i++){
        try{simulate(i);}
        catch(const DatabaseException& e){cout<<"DB: "<<e.what()<<"\n";}
        catch(const NetworkException& e){cout<<"NET: "<<e.what()<<"\n";}
        catch(const AppException& e){cout<<"APP: "<<e.what()<<"\n";}
    }
}
Program 05
Template SafeArray with Bounds Checking
10 marks
Class template SafeArray<T> with operator[] throwing out_of_range on invalid index. Constructor throws invalid_argument for size<=0. at() method. Full main demo.
Show Solution ▾
template<typename T>
class SafeArray {
    T* data; int size;
public:
    SafeArray(int n):size(n){
        if(n<=0) throw invalid_argument("Size must be positive");
        data=new T[n]{};
    }
    ~SafeArray(){delete[] data;}
    T& operator[](int i){
        for(i<0||i>=size)
            throw out_of_range("Index "+to_string(i)+" out of [0,"+to_string(size-1)+"]");
        return data[i];
    }
    int getSize(){return size;}
};
int main(){
    try{
        SafeArray<int> arr(5);
        arr[0]=10; arr[4]=99;
        cout<<arr[0]<<" "<<arr[4]<<"\n";  // 10 99
        cout<<arr[7];                        // throws
    }
    catch(const out_of_range& e){cout<<"Range: "<<e.what();}
    catch(const invalid_argument& e){cout<<"Arg: "<<e.what();}
    catch(...){cout<<"Unknown";}
}
▶ Expected Output
10 99
Program 06 — Challenge
Banking System: Templates + Exceptions
15 marks
ATTEMPT ON YOUR OWN. Class template Account<T> where T is the balance type (int for basic, double for precise). Custom InsufficientFundsException (stores attempted amount, available amount, provides what()). Custom InvalidAmountException. Methods: deposit, withdraw, transfer — all with proper throws. Multiple accounts with different types.
Show Solution ▾
class InsufficientFundsException : public std::exception {
    double attempted, available;
public:
    InsufficientFundsException(double a, double v): attempted(a), available(v){}
    const char* what() const noexcept override {
        static string r;
        r="Insufficient funds! Tried: "+to_string(attempted)
          +" Available: "+to_string(available);
        return r.c_str();
    }
};
class InvalidAmountException : public std::exception {
public:
    const char* what() const noexcept override {return "Amount must be positive!";}
};

template<typename T>
class Account {
    T balance; string owner;
public:
    Account(string o, T b):owner(o),balance(b){}
    void deposit(T amt){
        if(amt<=0) throw InvalidAmountException();
        balance+=amt;
        cout<<owner<<" deposited "<<amt<<" | Balance: "<<balance<<"\n";
    }
    void withdraw(T amt){
        if(amt<=0) throw InvalidAmountException();
        if(amt>balance) throw InsufficientFundsException(amt,balance);
        balance-=amt;
        cout<<owner<<" withdrew "<<amt<<" | Balance: "<<balance<<"\n";
    }
    void transfer(Account<T>& to, T amt){
        withdraw(amt);
        to.deposit(amt);
    }
    T getBalance(){return balance;}
};

int main(){
    Account<double> alice("Alice",5000.0), bob("Bob",1000.0);
    try{
        alice.deposit(500);
        alice.transfer(bob,2000);
        alice.withdraw(99999);  // throws InsufficientFunds
    }
    catch(const InsufficientFundsException& e){cout<<e.what()<<"\n";}
    catch(const InvalidAmountException& e){cout<<e.what()<<"\n";}
}

💻

Complete Programs — Full Execution Flow

Every program is 100% compilable. Study the dry runs to understand template instantiation and exception propagation step-by-step.

Program 1 — Function Templates: Sort, Search, Min/Max (Generic)
#include <iostream>
using namespace std;

// Generic swap
template <typename T>
void mySwap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

// Generic bubble sort
template <typename T>
void bubbleSort(T arr[], int n) {
    for(int i = 0; i < n-1; i++)
        for(int j = 0; j < n-1-i; j++)
            if(arr[j] > arr[j+1])
                mySwap(arr[j], arr[j+1]);
}

// Generic print array
template <typename T>
void printArray(T arr[], int n, string label) {
    cout << label << ": ";
    for(int i = 0; i < n; i++) cout << arr[i] << " ";
    cout << "\n";
}

// Generic min and max
template <typename T>
T findMin(T arr[], int n) { T m=arr[0]; for(int i=1;i<n;i++) if(arr[i]<m) m=arr[i]; return m; }
template <typename T>
T findMax(T arr[], int n) { T m=arr[0]; for(int i=1;i<n;i++) if(arr[i]>m) m=arr[i]; return m; }

int main() {
    // --- Integer array ---
    int iArr[] = {5, 2, 8, 1, 9, 3};
    int n1 = 6;
    printArray(iArr, n1, "Before");
    bubbleSort(iArr, n1);
    printArray(iArr, n1, "After ");
    cout << "Min: " << findMin(iArr,n1) << "  Max: " << findMax(iArr,n1) << "\n\n";

    // --- Double array ---
    double dArr[] = {3.14, 1.41, 2.71, 0.57};
    int n2 = 4;
    printArray(dArr, n2, "Before");
    bubbleSort(dArr, n2);
    printArray(dArr, n2, "After ");
    cout << "Min: " << findMin(dArr,n2) << "  Max: " << findMax(dArr,n2) << "\n\n";

    // --- String array ---
    string sArr[] = {"banana","apple","cherry","date"};
    int n3 = 4;
    printArray(sArr, n3, "Before");
    bubbleSort(sArr, n3);
    printArray(sArr, n3, "After ");
    return 0;
}
▶ Expected Output
🧠 Deep Code Explanation
1
template <typename T> above each function — tells compiler "T is a placeholder, will be replaced when called". The compiler generates SEPARATE functions for each type used: bubbleSort<int>, bubbleSort<double>, bubbleSort<string>.
2
mySwap(arr[j], arr[j+1]) — compiler deduces T from argument types. Same swap logic works because int, double, string all support operator=.
3
String comparison in bubbleSort: arr[j] > arr[j+1] for strings uses lexicographic comparison (dictionary order). So "banana" > "apple" is true → they swap. Final order is alphabetical.
4
Three complete instantiations happen at compile time: bubbleSort<int>, bubbleSort<double>, bubbleSort<string>. Only these 3 exist in the binary.
✍️ How to Write This in Exam
1
Write generic function: put template<typename T> on the line BEFORE the function. Replace all concrete types with T.
2
In main: call WITHOUT angle brackets — compiler deduces type: bubbleSort(iArr,n) → T=int automatically.
3
For explicit call: bubbleSort<int>(iArr,n) — needed when compiler can't deduce or you want to force a type.
▶ Dry Run
bubbleSort(iArr,6) callT deduced as int. Generates: void bubbleSort(int arr[], int n)
i=0, j=0arr[0]=5, arr[1]=2. 5>2 → mySwap(5,2). temp=5,arr[0]=2,arr[1]=5. arr=[2,5,8,1,9,3]
i=0, j=4arr[4]=9, arr[5]=3. 9>3 → swap. arr=[2,5,8,1,3,9]
After all passesarr=[1,2,3,5,8,9]. findMin returns arr[0]=1, findMax returns arr[5]=9
bubbleSort(sArr,4)T=string. "banana">"apple" → true (b>a lexicographically) → swap. After all passes: alphabetical order.

Program 2 — Class Template: Stack with Exception Handling
#include <iostream>
#include <stdexcept>
using namespace std;

template <typename T>
class Stack {
    T arr[100];
    int topIndex;
public:
    Stack() : topIndex(-1) {}

    void push(T val) {
        if(topIndex == 99)
            throw overflow_error("Stack Overflow! Max 100 elements.");
        arr[++topIndex] = val;
        cout << "Pushed: " << val << " (size=" << topIndex+1 << ")\n";
    }

    T pop() {
        if(topIndex == -1)
            throw underflow_error("Stack Underflow! Cannot pop empty stack.");
        T val = arr[topIndex--];
        cout << "Popped: " << val << " (size=" << topIndex+1 << ")\n";
        return val;
    }

    T peek() const {
        if(topIndex == -1) throw underflow_error("Stack is empty!");
        return arr[topIndex];
    }

    bool isEmpty() const { return topIndex == -1; }
    int  size()    const { return topIndex + 1; }

    void display() const {
        cout << "Stack (top→bottom): ";
        for(int i = topIndex; i >= 0; i--) cout << arr[i] << " ";
        cout << "\n";
    }
};

int main() {
    cout << "=== Integer Stack ===\n";
    Stack<int> s;
    s.push(10); s.push(20); s.push(30);
    s.display();
    cout << "Peek: " << s.peek() << "\n";
    s.pop(); s.pop();
    s.display();

    cout << "\n=== Exception Demo ===\n";
    try {
        s.pop();  // pops 10 — OK
        s.pop();  // underflow!
    }
    catch(const underflow_error& e) {
        cout << "Caught: " << e.what() << "\n";
    }

    cout << "\n=== String Stack ===\n";
    Stack<string> ss;
    ss.push("hello"); ss.push("world");
    ss.display();
    return 0;
}
▶ Expected Output
🧠 Deep Code Explanation
1
template <typename T> class Stack — one class definition, infinite types. T arr[100] means: if T=int, this is int[100]. If T=string, this is string[100]. The compiler generates the full class at instantiation.
2
Stack<int> s — compiler instantiates Stack<int>: generates a complete class with arr as int[100], push(int), pop() returning int, etc.
3
push(10): topIndex was -1. ++topIndex makes it 0. arr[0]=10. Size=0-(-1)... no: topIndex+1 = 0+1 = 1. ✓
4
pop() with empty stack: topIndex==-1 → throws underflow_error. Flow exits pop() immediately, searches catch blocks in main. underflow_error inherits from runtime_error inherits from exception.
5
Stack<string> ss — compiler generates SECOND complete Stack class with arr as string[100]. Two separate classes exist in binary.
✍️ How to Write This in Exam
1
Write the class normally for int first. Then add template<typename T> before class and replace int with T everywhere.
2
Don't forget: class name becomes Stack<T> in method definitions outside the class.
3
Add exceptions to make it production-quality: overflow on push when full, underflow on pop/peek when empty.
▶ Dry Run
Stack stopIndex=-1, arr[100] allocated as int array. isEmpty()=true
s.push(10)topIndex: -1→0. arr[0]=10. Print "Pushed: 10 (size=1)"
s.push(20)topIndex: 0→1. arr[1]=20. Print "Pushed: 20 (size=2)"
s.push(30)topIndex: 1→2. arr[2]=30. Print "Pushed: 30 (size=3)"
s.peek()topIndex=2, return arr[2]=30. No change.
s.pop()val=arr[2]=30, topIndex:2→1. Returns 30.
s.pop() 2ndval=arr[1]=20, topIndex:1→0. Returns 20.
s.pop() underflowtopIndex=0, pop removes 10(topIndex→-1). 2nd pop: topIndex==-1 → throw underflow_error("Stack Underflow!")
catch blockunderflow_error& caught. e.what() = "Stack Underflow! Cannot pop empty stack."

Program 3 — Custom Exception Hierarchy + Stack Unwinding Demo
#include <iostream>
#include <stdexcept>
using namespace std;

// Custom exception hierarchy
class AppError : public exception {
protected:
    string message;
public:
    AppError(string m) : message(m) {}
    const char* what() const noexcept override { return message.c_str(); }
};

class ValidationError : public AppError {
    string field;
public:
    ValidationError(string f, string m)
        : AppError("Validation failed on '" + f + "': " + m), field(f) {}
    string getField() { return field; }
};

class DatabaseError : public AppError {
    int errorCode;
public:
    DatabaseError(int code, string m)
        : AppError(m), errorCode(code) {}
    int getCode() { return errorCode; }
};

// RAII guard — destructor ALWAYS runs (stack unwinding demo)
struct TransactionGuard {
    string txName;
    bool committed;
    TransactionGuard(string n) : txName(n), committed(false) {
        cout << "[TX] Started: " << txName << "\n";
    }
    ~TransactionGuard() {
        if(!committed)
            cout << "[TX] ROLLED BACK: " << txName << "\n";
        else
            cout << "[TX] Committed: " << txName << "\n";
    }
    void commit() { committed = true; }
};

void saveUser(string name, int age) {
    TransactionGuard tx("saveUser");          // created on stack
    if(name.empty())
        throw ValidationError("name", "cannot be empty");
    for(age < 0 || age > 150)
        throw ValidationError("age", "must be 0-150");
    // Simulate DB operation
    if(name == "error")
        throw DatabaseError(500, "Internal DB failure");
    cout << "User saved: " << name << ", age=" << age << "\n";
    tx.commit();
}   // tx destructor runs here (or during unwinding)

int main() {
    // Test 1: success
    cout << "=== Test 1: Valid user ===\n";
    try { saveUser("Alice", 30); } catch(...) {}

    // Test 2: validation error
    cout << "\n=== Test 2: Empty name ===\n";
    try { saveUser("", 25); }
    catch(const ValidationError& e) {
        cout << "Validation: " << e.what() << "\n";
    }

    // Test 3: DB error
    cout << "\n=== Test 3: DB Error ===\n";
    try { saveUser("error", 25); }
    catch(const DatabaseError& e) {
        cout << "Database: " << e.what() << "\n";
    }
    catch(const AppError& e) {     // catches anything else AppError-derived
        cout << "App: " << e.what() << "\n";
    }
    return 0;
}
▶ Expected Output
🧠 Deep Code Explanation
1
Exception hierarchy: AppError → base. ValidationError and DatabaseError inherit from AppError. This lets you catch(AppError&) to catch BOTH, or catch specific type for specific handling.
2
TransactionGuard demonstrates RAII + stack unwinding. Constructor starts TX. Destructor checks if committed — if not, rolls back. Destructor ALWAYS runs whether function returns normally or throws.
3
Test 2: saveUser("",25) — creates tx (prints "Started"). name.empty()=true → throw ValidationError. Stack unwinds: tx destructor runs → "ROLLED BACK". Then catch block in main prints the error.
4
Test 3: "error" name passes validation, reaches DatabaseError throw. Same unwinding pattern — tx rolls back automatically. catch(DatabaseError) matches since it's the exact type. If it didn't match, catch(AppError) would catch it instead.
5
const char* what() const noexcept override — overrides the base exception::what(). noexcept means what() itself never throws. message.c_str() returns the internal C-string.
✍️ How to Write This in Exam
1
Build exception class: inherit exception, add string msg, constructor sets it, override what() returning msg.c_str()
2
For hierarchy: write base exception class first, then derived. Derived constructor calls base: AppError("Validation failed...") via initializer list.
3
Catch order: MOST SPECIFIC first (DatabaseError before AppError). Reverse = wrong catch fires.
4
Always catch by const reference: catch(const ValidationError& e) — avoids copy + slicing.

🚀 Live C++ Compiler