Function Templates
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
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'); }
7gtemplate <typename T> T add(T a, T b){ return a+b; }
Class Templates
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
}
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 <class T> class Pair { public: T first, second; Pair(T a, T b){ first=a; second=b; } };
int main(){ Pair<int> p(10, 20); }
Template Specialization
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 |
template <> class Pair<char> { /* special logic for char */ };
Exception Handling — try, catch, throw
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!
}
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
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; }
UnderageMultiple Catch Blocks
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
(...) 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
}
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"; }
IntCustom Exception Classes
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!
}
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)
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"; } };
Stack Unwinding & Exception Safety
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
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
}
void func(){ Test t; throw 1; } // t destroyed before catch
Exam Questions
2-Mark Questions
Q1. What is a function template? Give syntax.
template <typename T> T func(T a) { ... }. Compiler generates
actual functions via template instantiation.Q2. What is template specialization?
template<> void func<int>(int x){...}. Partial: specializing for a
subset of type combinations.Q3. What are try, catch, throw?
Q4. What is stack unwinding?
5-Mark Questions
Q5. Write a class template for a Stack with push and pop operations.
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?
Q7. How do you create a custom exception class? Why inherit from std::exception?
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
#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;
}
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.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.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.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.#include <iostream> and using namespace std; first. For string operations also add #include <string>.template <typename T> then next line the function. No blank line between them.template<> (empty brackets), then function with explicit type in angle brackets: bool maxOf<bool>(bool a, bool b).template<T> without the typename keyword. Always: template<typename T> or template<class T>.Complete Program — Class Templates
#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;
}
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.Stack() : top(-1) {} — initializer list sets top to -1, meaning "empty". index 0..49 are valid positions. top tracks the last used index.template <typename T> on the line before class Stack {. Inside class, replace every concrete type with T.template <typename T> and use Stack<T>:: scope.Stack<int> s — type must be explicit in angle brackets (unlike function templates where compiler deduces).Complete Program — Exception Handling + Custom Exception
#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;
}
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.TxGuard tx("withdraw") — RAII object. Constructor prints "Start". If exception occurs before tx.commit(), destructor fires during stack unwinding and prints "Rollback".class MyEx : public exception { string msg; public: MyEx(string m):msg(m){} const char* what() const noexcept override { return msg.c_str(); } };if(const MyEx& e). Never by value — avoids slicing and copy overhead.#include <stdexcept> for standard exceptions (invalid_argument, out_of_range, overflow_error, underflow_error, runtime_error).Practice — Function Templates
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
}
10 5
world
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
}
9
z
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
}
3
template <typename T> on the line BEFORE the function. Replace all type names
with T.a > b, T must have operator>. If you do a + b, T must have
operator+. Mention this in viva!template <typename T, typename U>. For return type that differs: use
auto or a third template param.template <typename T> → replace the type with T → done. The compiler writes the
actual functions for you at compile time."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?
3. When is a template function actually compiled?
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; }Practice — Class Templates
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)
}
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
}
3
2
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(); }
}
2
9
template<typename T> before the
class.template<typename T> prefix AND use ClassName<T>:: instead
of just ClassName::.T{} — this
zero-initializes for int (gives 0), empty-initializes for string (gives ""), etc.Stack<int>,
Stack<string> — always specify type in angle brackets for class templates
(unlike function templates where compiler deduces).template<typename T> prefix +
ClassName<T>:: scope. Miss either one → compile error."Practice — Exception Handling
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";
}
}
5
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"; }
}
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
}
Inner: HTTP 404 Outer: code 404
throw; inside catch block
— rethrows the original exception unchanged.catch(const MyException& e) — prevents slicing, efficient, correct
polymorphism.Predict Output & Debug — Templates + Exceptions
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 called7Each 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.
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 exceptioni=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!
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: A5 is int → specialized version → 5*2=10. 3.14 is double → generic. true is bool → specialized → "YES". 'A' is char → generic.
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: 42G1 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!
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
}
Line B
Show Fix ▾
Bug 1 — Line A: Missingtypename 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>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"; }
}
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 FIRSTif(Base& e) { ... } // Base after
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 emptytemplate<> and specify the type in the function name
angle brackets.Fix:
template<>string square<string>(string s) { return s + s; }
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.Edge Cases, STL Connection & Active Recall
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);
}
WRONG! Compares addresses not values
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
}
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
4. noexcept Violation
void safe() noexcept {
throw 42; // NEVER do this! Promises not to throw, but does
// Result: std::terminate() called immediately!
}
Everything in STL is built on templates — that's why it works with ANY type:
// These are all template instantiations:
vector<int> // compiler generates vector class for int
vector<string> // compiler generates vector class for string
pair<int,string> // two-type template
sort(v.begin(),v.end()); // template function, deduces iterator type
// Without templates, STL would need separate classes for each type:
// IntVector, DoubleVector, StringVector... impossibly verbose!
Real-world impact: Templates enable generic programming —
write once, use with any type. This is the core philosophy behind STL and modern C++. When you write
vector<MyClass>, you're instantiating a template the same way you write your own
templates!
Active Recall — Test Yourself (No Notes)
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.
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?
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?
7. What does noexcept do? What happens if a noexcept function throws?
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?
10. Template vs overloading — when to use each?
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().
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
}
1 2 5 8 9
3
1
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
}
1
2
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
}
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";}
}
}
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";}
}
10 99
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.
#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;
}
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>.mySwap(arr[j], arr[j+1]) — compiler deduces T from argument
types. Same swap logic works because int, double, string all support operator=.arr[j] > arr[j+1] for
strings uses lexicographic comparison (dictionary order). So "banana" > "apple" is true →
they swap. Final order is alphabetical.template<typename T> on
the line BEFORE the function. Replace all concrete types with T.bubbleSort(iArr,n) → T=int automatically.bubbleSort<int>(iArr,n) — needed
when compiler can't deduce or you want to force a type.#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;
}
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.Stack<int> s — compiler instantiates Stack<int>:
generates a complete class with arr as int[100], push(int), pop() returning int, etc.template<typename T> before class and replace int with T everywhere.
Stack<T> in method
definitions outside the class.#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;
}
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.catch(const ValidationError& e) — avoids copy + slicing.