Skip to content

Class_templates

Class templates create generic classes that work with any type, enabling type-safe containers and custom generic data structures.

Class templates are similar to function templates but define how entire classes can be parameterized. The standard library containers like std::vector and std::map are class templates.

#include <iostream>
#include <string>
template<typename T>
class Box {
private:
T value_;
public:
explicit Box(T v) : value_(v) {}
T getValue() const { return value_; }
void setValue(T v) { value_ = v; }
void print() const {
std::cout << "Box contains: " << value_ << "\n";
}
};
int main() {
Box<int> intBox(42);
Box<std::string> strBox("Hello");
Box<double> dblBox(3.14);
intBox.print(); // Box contains: 42
strBox.print(); // Box contains: Hello
dblBox.print(); // Box contains: 3.14
return 0;
}
#include <iostream>
#include <string>
#include <utility>
template<typename T1, typename T2>
class Pair {
private:
T1 first_;
T2 second_;
public:
Pair() : first_(T1{}), second_(T2{}) {}
Pair(T1 first, T2 second) : first_(first), second_(second) {}
T1& first() { return first_; }
const T1& first() const { return first_; }
T2& second() { return second_; }
const T2& second() const { return second_; }
void print() const {
std::cout << "(" << first_ << ", " << second_ << ")\n";
}
};
int main() {
Pair<int, double> p1(1, 2.5);
Pair<std::string, int> p2("Age", 30);
p1.print(); // (1, 2.5)
p2.print(); // (Age, 30)
return 0;
}
#include <iostream>
#include <string>
#include <utility>
template<typename T1, typename T2 = std::string>
class Record {
T1 key_;
T2 value_;
public:
Record(T1 k, T2 v) : key_(k), value_(v) {}
T1 getKey() const { return key_; }
T2 getValue() const { return value_; }
};
int main() {
Record<int> r1(1, "One"); // Record<int, std::string>
Record<int, int> r2(2, 42); // Both specified
return 0;
}
#include <iostream>
template<typename T>
class Container {
T data_;
public:
explicit Container(T d) : data_(d) {}
// Regular member function
void print() const {
std::cout << data_ << "\n";
}
// Member function template
template<typename U>
U convert() const {
return static_cast<U>(data_);
}
// Member function template with different type
template<typename U>
Container<U> transform(U (*fn)(T)) {
return Container<U>(fn(data_));
}
};
int main() {
Container<int> c(42);
c.print();
// Convert to double
double d = c.convert<double>();
std::cout << d << "\n"; // 42
// Transform
auto c2 = c.transform([](int x) { return x * 2.0; });
c2.print(); // 84
return 0;
}
#include <iostream>
template<typename T>
class Counter {
private:
static int count_; // Shared across all instances of Counter<T>
public:
Counter() { ++count_; }
~Counter() { --count_; }
static int getCount() { return count_; }
};
// Define static member for each instantiation
template<typename T>
int Counter<T>::count_ = 0;
int main() {
Counter<int> c1, c2, c3;
std::cout << Counter<int>::getCount() << "\n"; // 3
Counter<double> d1;
std::cout << Counter<int>::getCount() << "\n"; // Still 3
std::cout << Counter<double>::getCount() << "\n"; // 1
return 0;
}
#include <iostream>
template<typename T>
class Base {
protected:
T value_;
public:
Base(T v) : value_(v) {}
virtual void print() const = 0;
};
template<typename T>
class Derived : public Base<T> {
public:
Derived(T v) : Base<T>(v) {}
void print() const override {
std::cout << "Derived: " << Base<T>::value_ << "\n";
}
};
int main() {
Derived<int> d(42);
d.print(); // Derived: 42
return 0;
}
#include <iostream>
#include <string>
template<typename T>
class Storage {
protected:
T data_;
public:
Storage(T d) : data_(d) {}
virtual void save() const = 0;
};
// Generic implementation
template<typename T>
class FileStorage : public Storage<T> {
std::string filename_;
public:
FileStorage(T d, const std::string& f) : Storage<T>(d), filename_(f) {}
void save() const override {
std::cout << "Saving to " << filename_ << ": " << this->data_ << "\n";
}
};
// Specialization for pointers
template<typename T>
class FileStorage<T*> : public Storage<T*> {
std::string filename_;
public:
FileStorage(T* d, const std::string& f) : Storage<T*>(d), filename_(f) {}
void save() const override {
std::cout << "Saving pointer to " << filename_ << "\n";
}
};
int main() {
FileStorage<int> fs(42, "data.txt");
fs.save(); // Saving to data.txt: 42
int x = 100;
FileStorage<int*> fp(&x, "ptr.txt");
fp.save(); // Saving pointer to ptr.txt
return 0;
}
#include <iostream>
#include <vector>
template<typename T>
class Matrix {
std::vector<std::vector<T>> data_;
size_t rows_, cols_;
public:
Matrix(size_t r, size_t c, T init = T{})
: rows_(r), cols_(c) {
data_.resize(r);
for (auto& row : data_) {
row.resize(c, init);
}
}
T& at(size_t r, size_t c) { return data_[r][c]; }
const T& at(size_t r, size_t c) const { return data_[r][c]; }
// Nested iterator
class Iterator {
Matrix& matrix_;
size_t row_, col_;
public:
Iterator(Matrix& m, size_t r, size_t c) : matrix_(m), row_(r), col_(c) {}
T& operator*() { return matrix_.at(row_, col_); }
Iterator& operator++() {
++col_;
if (col_ >= matrix_.cols()) {
col_ = 0;
++row_;
}
return *this;
}
bool operator!=(const Iterator& other) const {
return row_ != other.row_ || col_ != other.col_;
}
};
Iterator begin() { return Iterator(*this, 0, 0); }
Iterator end() { return Iterator(*this, rows_, 0); }
};
int main() {
Matrix<int> m(3, 3, 0);
m.at(0, 0) = 1;
m.at(1, 1) = 2;
m.at(2, 2) = 3;
for (auto& val : m) {
std::cout << val << " ";
}
std::cout << "\n";
return 0;
}
#include <iostream>
template<typename T>
class Point {
T x_, y_;
public:
Point(T x, T y) : x_(x), y_(y) {}
// Friend function template declaration
template<typename U>
friend std::ostream& operator<<(std::ostream& os, const Point<U>& p) {
os << "(" << p.x_ << ", " << p.y_ << ")";
return os;
}
};
int main() {
Point<int> p1(1, 2);
Point<double> p2(1.5, 2.5);
std::cout << p1 << "\n"; // (1, 2)
std::cout << p2 << "\n"; // (1.5, 2.5)
return 0;
}
#include <iostream>
template<typename T>
class Vector2D {
T x_, y_;
public:
Vector2D(T x, T y) : x_(x), y_(y) {}
// Addition operator
Vector2D operator+(const Vector2D& other) const {
return Vector2D(x_ + other.x_, y_ + other.y_);
}
// Scalar multiplication
Vector2D operator*(T scalar) const {
return Vector2D(x_ * scalar, y_ * scalar);
}
// Stream output
friend std::ostream& operator<<(std::ostream& os, const Vector2D& v) {
os << "(" << v.x_ << ", " << v.y_ << ")";
return os;
}
};
int main() {
Vector2D<int> v1(1, 2);
Vector2D<int> v2(3, 4);
Vector2D<int> v3 = v1 + v2;
std::cout << v3 << "\n"; // (4, 6)
Vector2D<int> v4 = v1 * 3;
std::cout << v4 << "\n"; // (3, 6)
return 0;
}
#include <iostream>
#include <vector>
#include <type_traits>
template<typename T>
class Container {
std::vector<T> data_;
public:
using value_type = T; // Type alias
void add(const T& value) {
data_.push_back(value);
}
const T& get(size_t index) const {
return data_[index];
}
size_t size() const { return data_.size(); }
};
int main() {
Container<int> c;
c.add(1);
c.add(2);
c.add(3);
// Use type alias
Container<int>::value_type x = c.get(0);
std::cout << x << "\n";
// Use with type traits
static_assert(std::is_same_v<Container<int>::value_type, int>);
return 0;
}

CRTP (Curiously Recurring Template Pattern)

Section titled “CRTP (Curiously Recurring Template Pattern)”
#include <iostream>
template<typename Derived>
class Base {
public:
void interface() {
// Call derived class method
static_cast<Derived*>(this)->implementation();
}
void print() {
std::cout << "Base::print called\n";
}
};
class Derived : public Base<Derived> {
public:
void implementation() {
std::cout << "Derived::implementation called\n";
}
};
int main() {
Derived d;
d.interface(); // Calls Derived::implementation
d.print(); // Calls Base::print
return 0;
}
  • Class templates enable generic container types
  • Support multiple template parameters with defaults
  • Member function templates enable flexible operations
  • Static members are per-template-instantiation
  • Friend function templates enable operator overloading
  • CRTP enables compile-time polymorphism
  • Type aliases improve code readability