Programming Language & Its Applications β Generic Programming and Exception Handling, NEC licence examination syllabus (Nepal Engineering Council).
A blueprint for a family of classes β one Stack<T> definition serving Stack<int>, Stack<string> and every type you need.
std::vector<T>, std::map<K,V>, std::unique_ptr<T>, std::optional<T> β each is one class definition that the compiler stamps out for whatever type you name, with no runtime cost. Before templates, C++ programmers wrote a separate IntStack, StringStack, and DoubleStack, or stored void* and cast everywhere, losing all type safety. The entire STL exists because of this feature. Search "why std::vector is faster than Java ArrayList" β the answer is that vector<int> stores actual ints contiguously while ArrayList<Integer> stores pointers to boxed objects scattered across the heap.#include <iostream>
#include <string>
#include <stdexcept>
template <typename T, int CAPACITY = 100>
class Stack {
T data[CAPACITY];
int topIndex;
public:
Stack() : topIndex(-1) {}
bool push(const T &v) {
if (full()) return false;
data[++topIndex] = v;
return true;
}
bool pop(T &out) {
if (empty()) return false;
out = data[topIndex--];
return true;
}
const T& peek() const {
if (empty()) throw std::out_of_range("stack is empty");
return data[topIndex];
}
bool empty() const { return topIndex == -1; }
bool full() const { return topIndex == CAPACITY - 1; }
int size() const { return topIndex + 1; }
static int capacity() { return CAPACITY; }
};
int main() {
/* Stack of ints, capacity from the default */
Stack<int> si;
for (int i = 10; i <= 50; i += 10) si.push(i);
std::cout << " int stack size=" << si.size()
<< " top=" << si.peek() << "\n popping: ";
int v;
while (si.pop(v)) std::cout << v << " ";
std::cout << "\n";
/* Stack of strings β same code, no changes */
Stack<std::string> ss;
ss.push("Ram"); ss.push("Sita"); ss.push("Hari");
std::cout << " string stack: ";
std::string s;
while (ss.pop(s)) std::cout << s << " ";
std::cout << "\n";
/* explicit small capacity β a DIFFERENT type */
Stack<char, 3> sc;
std::cout << " char stack capacity=" << sc.capacity() << ": ";
for (char c = 'A'; c <= 'E'; c++)
std::cout << c << (sc.push(c) ? "+ " : "X ");
std::cout << "\n";
/* sizes differ per instantiation */
std::cout << " sizeof(Stack<char,3>) = "
<< sizeof(Stack<char,3>) << "\n";
std::cout << " sizeof(Stack<int,10>) = "
<< sizeof(Stack<int,10>) << "\n";
/* nested templates */
Stack<Stack<int,4>, 2> nested;
Stack<int,4> inner; inner.push(7); inner.push(8);
nested.push(inner);
Stack<int,4> got;
nested.pop(got);
int x; got.pop(x);
std::cout << " nested stack gave " << x << "\n";
/* exception from peek on an empty stack */
Stack<int> e;
try { e.peek(); }
catch (const std::out_of_range &ex) {
std::cout << " caught: " << ex.what() << "\n";
}
return 0;
}
Output:
int stack size=5 top=50
popping: 50 40 30 20 10
string stack: Hari Sita Ram
char stack capacity=3: A+ B+ C+ DX EX
sizeof(Stack<char,3>) = 8
sizeof(Stack<int,10>) = 44
nested stack gave 8
caught: stack is empty
static member of a class template gets one copy per instantiation, not one overall. Stack<int>::count and Stack<double>::count are two independent variables. That is usually what you want, but it means "a class-wide counter" now counts per type.
#include <iostream>
#include <string>
#include <iomanip>
#include <stdexcept>
/* two type parameters */
template <typename K, typename V>
class Pair {
K key;
V value;
public:
Pair(const K &k, const V &v) : key(k), value(v) {}
const K& first() const { return key; }
const V& second() const { return value; }
void set(const V &v) { value = v; }
/* a friend template needs its own parameters */
template <typename A, typename B>
friend std::ostream& operator<<(std::ostream&,
const Pair<A,B>&);
};
template <typename A, typename B>
std::ostream& operator<<(std::ostream &os, const Pair<A,B> &p) {
return os << "(" << p.key << " -> " << p.value << ")";
}
/* type + two non-type parameters */
template <typename T, int ROWS, int COLS>
class Matrix {
T m[ROWS][COLS];
public:
Matrix() { for (int i=0;i<ROWS;i++) for (int j=0;j<COLS;j++)
m[i][j] = T{}; }
T& at(int i, int j) {
if (i<0||i>=ROWS||j<0||j>=COLS)
throw std::out_of_range("matrix index");
return m[i][j];
}
const T& at(int i, int j) const {
if (i<0||i>=ROWS||j<0||j>=COLS)
throw std::out_of_range("matrix index");
return m[i][j];
}
/* multiplication is only valid for compatible shapes β
and the TYPE SYSTEM enforces it */
template <int C2>
Matrix<T, ROWS, C2> operator*(const Matrix<T, COLS, C2> &b) const {
Matrix<T, ROWS, C2> r;
for (int i = 0; i < ROWS; i++)
for (int j = 0; j < C2; j++)
for (int k = 0; k < COLS; k++)
r.at(i,j) += m[i][k] * b.at(k,j);
return r;
}
void print(const char *tag) const {
std::cout << " " << tag << " (" << ROWS << "x" << COLS << "):\n";
for (int i = 0; i < ROWS; i++) {
std::cout << " ";
for (int j = 0; j < COLS; j++)
std::cout << std::setw(6) << m[i][j];
std::cout << "\n";
}
}
};
int main() {
Pair<std::string, int> p1("Ram", 87);
Pair<int, double> p2(101, 91.5);
Pair<char, std::string> p3('A', "excellent");
std::cout << " " << p1 << " " << p2 << " " << p3 << "\n";
Matrix<int,2,3> A;
A.at(0,0)=1; A.at(0,1)=2; A.at(0,2)=3;
A.at(1,0)=4; A.at(1,1)=5; A.at(1,2)=6;
Matrix<int,3,2> B;
B.at(0,0)=7; B.at(0,1)=8;
B.at(1,0)=9; B.at(1,1)=10;
B.at(2,0)=11; B.at(2,1)=12;
A.print("A"); B.print("B");
auto C = A * B;
C.print("A*B");
/* Matrix<int,2,3> * Matrix<int,2,3> does NOT compile β
the shapes are checked by the TYPE SYSTEM, at compile
time, not by a runtime if-statement */
/* auto bad = A * A; error: no matching operator* */
try { A.at(5, 0); }
catch (const std::out_of_range &e) {
std::cout << " caught: " << e.what() << "\n";
}
return 0;
}
Output:
(Ram -> 87) (101 -> 91.5) (A -> excellent)
A (2x3):
1 2 3
4 5 6
B (3x2):
7 8
9 10
11 12
A*B (2x2):
58 64
139 154
caught: matrix index
#include <iostream>
#include <string>
#include <utility>
#include <stdexcept>
template <typename T>
class Vec {
T *buf;
size_t n; /* elements in use */
size_t cap; /* allocated slots */
void grow(size_t want) {
size_t nc = cap ? cap * 2 : 4;
while (nc < want) nc *= 2;
T *nb = new T[nc];
for (size_t i = 0; i < n; i++) nb[i] = std::move(buf[i]);
delete[] buf;
buf = nb; cap = nc;
std::cout << " [grew to cap " << cap << "]\n";
}
public:
Vec() : buf(nullptr), n(0), cap(0) {}
/* Rule of Five for a class owning a raw resource */
~Vec() { delete[] buf; }
Vec(const Vec &o) : buf(new T[o.cap]), n(o.n), cap(o.cap) {
for (size_t i = 0; i < n; i++) buf[i] = o.buf[i];
std::cout << " [copy ctor: " << n << " elements]\n";
}
Vec(Vec &&o) noexcept
: buf(o.buf), n(o.n), cap(o.cap) { /* STEAL */
o.buf = nullptr; o.n = o.cap = 0;
std::cout << " [move ctor: stole the buffer]\n";
}
Vec& operator=(const Vec &o) {
if (this == &o) return *this;
T *nb = new T[o.cap];
for (size_t i = 0; i < o.n; i++) nb[i] = o.buf[i];
delete[] buf;
buf = nb; n = o.n; cap = o.cap;
return *this;
}
Vec& operator=(Vec &&o) noexcept {
if (this == &o) return *this;
delete[] buf;
buf = o.buf; n = o.n; cap = o.cap;
o.buf = nullptr; o.n = o.cap = 0;
return *this;
}
void push_back(const T &v) {
if (n == cap) grow(n + 1);
buf[n++] = v;
}
T& operator[](size_t i) {
if (i >= n) throw std::out_of_range("Vec index");
return buf[i];
}
const T& operator[](size_t i) const {
if (i >= n) throw std::out_of_range("Vec index");
return buf[i];
}
size_t size() const { return n; }
size_t capacity() const { return cap; }
};
template <typename T>
void dump(const char *tag, const Vec<T> &v) {
std::cout << " " << tag << " size=" << v.size()
<< " cap=" << v.capacity() << " : ";
for (size_t i = 0; i < v.size(); i++) std::cout << v[i] << " ";
std::cout << "\n";
}
int main() {
Vec<int> a;
for (int i = 1; i <= 9; i++) a.push_back(i * i);
dump("a", a);
Vec<int> b = a; /* copy */
b[0] = 999;
dump("b (copied, modified)", b);
dump("a (untouched)", a);
Vec<int> c = std::move(a); /* move */
dump("c (moved from a)", c);
std::cout << " a after move: size=" << a.size() << "\n";
Vec<std::string> s;
s.push_back("Ram"); s.push_back("Sita");
dump("strings", s);
try { std::cout << c[100]; }
catch (const std::out_of_range &e) {
std::cout << " caught: " << e.what() << "\n";
}
return 0;
}
Output:
[grew to cap 4]
[grew to cap 8]
[grew to cap 16]
a size=9 cap=16 : 1 4 9 16 25 36 49 64 81
[copy ctor: 9 elements]
b (copied, modified) size=9 cap=16 : 999 4 9 16 25 36 49 64 81
a (untouched) size=9 cap=16 : 1 4 9 16 25 36 49 64 81
[move ctor: stole the buffer]
c (moved from a) size=9 cap=16 : 1 4 9 16 25 36 49 64 81
a after move: size=0
[grew to cap 4]
strings size=2 cap=4 : Ram Sita
caught: Vec index
Vec above is a teaching version of std::vector, and comparing them is instructive. The real one uses an allocator so you can control where memory comes from, uses std::uninitialized_copy so it never default-constructs elements it has merely reserved, and provides iterators so it works with every STL algorithm. It also handles the case where a copy constructor throws halfway through a reallocation without leaking or corrupting β that is called strong exception safety, and it is the hardest part. Search "implementing std::vector exception safety" and "why vector growth factor is 2 or 1.5".Stack<T> with push, pop, empty and full is the standard question. State that the type argument is mandatory (Stack<int> s; not Stack s;) and that each instantiation is a separate, unrelated class with its own statics. Know non-type parameters (template <typename T, int N>) and that they must be compile-time constants β that is how std::array stores its size for free. Be ready to explain the doubling growth strategy and its amortised O(1) cost.Create a free account to tick topics off, take notes as you read, watch the video lessons and get a day-by-day study plan built around your exam date.
Loadingβ¦