cpp
class templates
01_class_templates.cpp⚙️cpp
/**
* ============================================================
* C++ CLASS TEMPLATES
* ============================================================
*
* This file covers:
* - Class template basics
* - Template member functions
* - Template specialization
* - Partial specialization
* - Templates with inheritance
* - Static members in templates
*
* Compile: g++ -std=c++17 -Wall 01_class_templates.cpp -o class_templates
* Run: ./class_templates
*
* ============================================================
*/
#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include <type_traits>
using namespace std;
// ============================================================
// PART 1: BASIC CLASS TEMPLATE
// ============================================================
template <typename T>
class Box {
private:
T value;
public:
// Constructor
Box(const T& v = T()) : value(v) {}
// Getter
T getValue() const { return value; }
// Setter
void setValue(const T& v) { value = v; }
// Display
void display() const {
cout << "Box contains: " << value << endl;
}
};
// ============================================================
// PART 2: CLASS TEMPLATE WITH MULTIPLE PARAMETERS
// ============================================================
template <typename K, typename V>
class Pair {
private:
K key;
V value;
public:
Pair(const K& k, const V& v) : key(k), value(v) {}
K getKey() const { return key; }
V getValue() const { return value; }
void setKey(const K& k) { key = k; }
void setValue(const V& v) { value = v; }
void display() const {
cout << key << " => " << value << endl;
}
};
// ============================================================
// PART 3: CLASS TEMPLATE WITH NON-TYPE PARAMETERS
// ============================================================
template <typename T, size_t CAPACITY>
class Stack {
private:
T data[CAPACITY];
size_t topIndex;
public:
Stack() : topIndex(0) {}
bool empty() const { return topIndex == 0; }
bool full() const { return topIndex == CAPACITY; }
size_t size() const { return topIndex; }
size_t capacity() const { return CAPACITY; }
void push(const T& value) {
if (full()) {
throw overflow_error("Stack is full");
}
data[topIndex++] = value;
}
T pop() {
if (empty()) {
throw underflow_error("Stack is empty");
}
return data[--topIndex];
}
const T& top() const {
if (empty()) {
throw underflow_error("Stack is empty");
}
return data[topIndex - 1];
}
void display() const {
cout << "Stack [" << size() << "/" << CAPACITY << "]: ";
for (size_t i = 0; i < topIndex; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
// ============================================================
// PART 4: MEMBER FUNCTION TEMPLATES
// ============================================================
template <typename T>
class Container {
private:
vector<T> items;
public:
void add(const T& item) {
items.push_back(item);
}
size_t size() const { return items.size(); }
// Member function template
template <typename U>
void addConverted(const U& item) {
items.push_back(static_cast<T>(item));
}
// Template member for comparison with different container
template <typename U>
bool hasSameSize(const Container<U>& other) const {
return size() == other.size();
}
void display() const {
cout << "Container [" << items.size() << "]: ";
for (const auto& item : items) {
cout << item << " ";
}
cout << endl;
}
};
// ============================================================
// PART 5: TEMPLATE SPECIALIZATION
// ============================================================
// Primary template
template <typename T>
class TypeInfo {
public:
static string name() { return "unknown"; }
static bool isNumeric() { return false; }
static bool isPointer() { return false; }
};
// Full specialization for int
template <>
class TypeInfo<int> {
public:
static string name() { return "int"; }
static bool isNumeric() { return true; }
static bool isPointer() { return false; }
};
// Full specialization for double
template <>
class TypeInfo<double> {
public:
static string name() { return "double"; }
static bool isNumeric() { return true; }
static bool isPointer() { return false; }
};
// Full specialization for string
template <>
class TypeInfo<string> {
public:
static string name() { return "string"; }
static bool isNumeric() { return false; }
static bool isPointer() { return false; }
};
// Partial specialization for pointers
template <typename T>
class TypeInfo<T*> {
public:
static string name() { return "pointer to " + TypeInfo<T>::name(); }
static bool isNumeric() { return false; }
static bool isPointer() { return true; }
};
// ============================================================
// PART 6: PARTIAL SPECIALIZATION
// ============================================================
// Primary template
template <typename T1, typename T2>
class Wrapper {
public:
void info() const {
cout << "Generic Wrapper<T1, T2>" << endl;
}
};
// Partial specialization: both same type
template <typename T>
class Wrapper<T, T> {
public:
void info() const {
cout << "Wrapper<T, T> - same types" << endl;
}
};
// ⚠️ LEARNING NOTE: Partial specializations can cause AMBIGUITY!
//
// PROBLEM: If we have both Wrapper<T,T> and Wrapper<T,int>, then
// Wrapper<int, int> matches BOTH equally well:
// - Wrapper<T, T> where T=int ✓
// - Wrapper<T, int> where T=int ✓
// The compiler can't choose → ERROR!
//
// SOLUTION: Add a FULL specialization for the ambiguous case.
// Full specialization (no template params) is MOST SPECIFIC and wins.
// Full specialization for <int, int> - resolves the ambiguity!
template <>
class Wrapper<int, int> {
public:
void info() const {
cout << "Wrapper<int, int> - FULL specialization (resolves ambiguity)" << endl;
}
};
// Partial specialization: second is int
template <typename T>
class Wrapper<T, int> {
public:
void info() const {
cout << "Wrapper<T, int> - second is int" << endl;
}
};
// Partial specialization: both pointers
template <typename T1, typename T2>
class Wrapper<T1*, T2*> {
public:
void info() const {
cout << "Wrapper<T1*, T2*> - both pointers" << endl;
}
};
// ============================================================
// PART 7: TEMPLATE WITH DEFAULT PARAMETERS
// ============================================================
template <typename T, typename Allocator = allocator<T>>
class SimpleVector {
private:
T* data;
size_t sz;
size_t cap;
Allocator alloc;
public:
SimpleVector(size_t initialCap = 10)
: sz(0), cap(initialCap) {
data = alloc.allocate(cap);
}
~SimpleVector() {
for (size_t i = 0; i < sz; i++) {
alloc.destroy(&data[i]);
}
alloc.deallocate(data, cap);
}
void push_back(const T& value) {
if (sz >= cap) {
// Simplified - no resize
throw overflow_error("Vector full");
}
alloc.construct(&data[sz++], value);
}
T& operator[](size_t i) { return data[i]; }
size_t size() const { return sz; }
};
// ============================================================
// PART 8: TEMPLATES AND INHERITANCE
// ============================================================
// Base template
template <typename T>
class BaseContainer {
protected:
T value;
public:
BaseContainer(const T& v) : value(v) {}
virtual void display() const {
cout << "Base: " << value << endl;
}
virtual ~BaseContainer() = default;
};
// Derived template inheriting from template
template <typename T>
class DerivedContainer : public BaseContainer<T> {
private:
string label;
public:
DerivedContainer(const T& v, const string& l)
: BaseContainer<T>(v), label(l) {}
void display() const override {
cout << label << ": " << this->value << endl;
}
};
// Non-template derived from template
class IntContainer : public BaseContainer<int> {
public:
IntContainer(int v) : BaseContainer<int>(v) {}
void display() const override {
cout << "Integer: " << value << endl;
}
};
// ============================================================
// PART 9: STATIC MEMBERS IN TEMPLATES
// ============================================================
template <typename T>
class Counter {
private:
T value;
static int instanceCount; // One per type!
public:
Counter(const T& v = T()) : value(v) {
instanceCount++;
}
~Counter() {
instanceCount--;
}
static int getCount() { return instanceCount; }
T getValue() const { return value; }
};
// Static member definition (one per type)
template <typename T>
int Counter<T>::instanceCount = 0;
// ============================================================
// MAIN FUNCTION
// ============================================================
int main() {
cout << "============================================" << endl;
cout << " C++ CLASS TEMPLATES" << endl;
cout << "============================================" << endl << endl;
// ========================================================
// DEMO 1: Basic Class Template
// ========================================================
cout << "--- DEMO 1: BASIC CLASS TEMPLATE ---" << endl << endl;
Box<int> intBox(42);
Box<double> doubleBox(3.14159);
Box<string> stringBox("Hello, Templates!");
intBox.display();
doubleBox.display();
stringBox.display();
cout << endl;
// ========================================================
// DEMO 2: Multiple Parameters
// ========================================================
cout << "--- DEMO 2: MULTIPLE PARAMETERS ---" << endl << endl;
Pair<string, int> age("Alice", 30);
Pair<int, double> coordinates(100, 3.14);
age.display();
coordinates.display();
cout << endl;
// ========================================================
// DEMO 3: Non-Type Parameters
// ========================================================
cout << "--- DEMO 3: NON-TYPE PARAMETERS ---" << endl << endl;
Stack<int, 5> intStack;
intStack.push(10);
intStack.push(20);
intStack.push(30);
intStack.display();
cout << "Top: " << intStack.top() << endl;
cout << "Pop: " << intStack.pop() << endl;
intStack.display();
Stack<string, 3> stringStack;
stringStack.push("first");
stringStack.push("second");
stringStack.display();
cout << endl;
// ========================================================
// DEMO 4: Member Function Templates
// ========================================================
cout << "--- DEMO 4: MEMBER TEMPLATES ---" << endl << endl;
Container<double> doubles;
doubles.add(1.5);
doubles.add(2.5);
doubles.addConverted(3); // int -> double
doubles.addConverted(4.9f); // float -> double
doubles.display();
Container<int> ints;
ints.add(1);
ints.add(2);
ints.add(3);
ints.add(4);
cout << "Same size? " << (doubles.hasSameSize(ints) ? "yes" : "no") << endl;
cout << endl;
// ========================================================
// DEMO 5: Full Specialization
// ========================================================
cout << "--- DEMO 5: FULL SPECIALIZATION ---" << endl << endl;
cout << "TypeInfo<int>::name() = " << TypeInfo<int>::name() << endl;
cout << "TypeInfo<double>::name() = " << TypeInfo<double>::name() << endl;
cout << "TypeInfo<string>::name() = " << TypeInfo<string>::name() << endl;
cout << "TypeInfo<char>::name() = " << TypeInfo<char>::name() << endl;
cout << "\nTypeInfo<int>::isNumeric() = " << TypeInfo<int>::isNumeric() << endl;
cout << "TypeInfo<string>::isNumeric() = " << TypeInfo<string>::isNumeric() << endl;
cout << "\nTypeInfo<int*>::name() = " << TypeInfo<int*>::name() << endl;
cout << "TypeInfo<int*>::isPointer() = " << TypeInfo<int*>::isPointer() << endl;
cout << endl;
// ========================================================
// DEMO 6: Partial Specialization
// ========================================================
cout << "--- DEMO 6: PARTIAL SPECIALIZATION ---" << endl << endl;
Wrapper<double, string> w1; // Uses generic Wrapper<T1, T2>
Wrapper<int, int> w2; // Uses FULL specialization (most specific)
Wrapper<double, int> w3; // Uses Wrapper<T, int>
Wrapper<int*, double*> w4; // Uses Wrapper<T1*, T2*>
Wrapper<char, char> w5; // Uses Wrapper<T, T> - same types
// ⚠️ LEARNING NOTE: Template specialization priority:
// 1. Full specialization (template<>) - MOST specific, always wins
// 2. Partial specialization - more specific patterns preferred
// 3. Primary template - least specific, used as fallback
w1.info(); // Generic
w2.info(); // Full specialization for <int,int>
w3.info(); // Second is int
w4.info(); // Both pointers
w5.info(); // Same types (Wrapper<T,T>)
cout << endl;
// ========================================================
// DEMO 7: Templates and Inheritance
// ========================================================
cout << "--- DEMO 7: TEMPLATE INHERITANCE ---" << endl << endl;
BaseContainer<double>* containers[] = {
new BaseContainer<double>(3.14),
new DerivedContainer<double>(2.72, "Euler"),
};
for (auto* c : containers) {
c->display();
delete c;
}
IntContainer intCont(42);
intCont.display();
cout << endl;
// ========================================================
// DEMO 8: Static Members
// ========================================================
cout << "--- DEMO 8: STATIC MEMBERS ---" << endl << endl;
cout << "Initial counts:" << endl;
cout << "Counter<int>: " << Counter<int>::getCount() << endl;
cout << "Counter<double>: " << Counter<double>::getCount() << endl;
{
Counter<int> c1(1), c2(2), c3(3);
Counter<double> d1(1.0);
cout << "\nAfter creating c1, c2, c3 (int) and d1 (double):" << endl;
cout << "Counter<int>: " << Counter<int>::getCount() << endl;
cout << "Counter<double>: " << Counter<double>::getCount() << endl;
}
cout << "\nAfter scope ends:" << endl;
cout << "Counter<int>: " << Counter<int>::getCount() << endl;
cout << "Counter<double>: " << Counter<double>::getCount() << endl;
cout << endl;
// ========================================================
// CLASS TEMPLATE SUMMARY
// ========================================================
cout << "--- CLASS TEMPLATE SYNTAX ---" << endl << endl;
cout << "Basic:" << endl;
cout << "─────────────────────────────────────────" << endl;
cout << "template <typename T>" << endl;
cout << "class MyClass { T member; };" << endl;
cout << "\nNon-type parameter:" << endl;
cout << "─────────────────────────────────────────" << endl;
cout << "template <typename T, size_t N>" << endl;
cout << "class Array { T data[N]; };" << endl;
cout << "\nFull specialization:" << endl;
cout << "─────────────────────────────────────────" << endl;
cout << "template <>" << endl;
cout << "class MyClass<int> { ... };" << endl;
cout << "\nPartial specialization:" << endl;
cout << "─────────────────────────────────────────" << endl;
cout << "template <typename T>" << endl;
cout << "class MyClass<T*> { ... };" << endl;
cout << endl;
cout << "============================================" << endl;
cout << "CLASS TEMPLATES COMPLETE!" << endl;
cout << "============================================" << endl;
return 0;
}
// ============================================================
// EXERCISES:
// ============================================================
/*
* 1. Create a template Queue<T, SIZE>:
* - Fixed-size circular queue
* - enqueue(), dequeue(), front(), back()
* - empty(), full(), size()
*
* 2. Create a SmartArray<T> template:
* - Dynamic array with bounds checking
* - Copy and move semantics
* - Iterator support
*
* 3. Create specialized Matrix<T>:
* - General template for any type
* - Specialized for bool (bit matrix)
* - Operations: add, multiply, transpose
*
* 4. Create a template linked list:
* - Node<T> and List<T>
* - insert, remove, find
* - Iterator class
*/