Class Templates — Stack, Queue, Template Member Functions, Friends
In this tutorial, you will learn about Class Templates. We cover key concepts, practical examples, and best practices to help you master this topic.
C++ class templates allow defining a family of classes parameterized by types or values, enabling type-safe reusable containers like std::vector<T> and std::array<T, N>.
What You'll Learn
You will write class templates with type and non-type parameters, define template member functions outside the class body, create template friend functions, specialize member functions for specific types, and understand how class templates interact with inheritance and composition.
Why It Matters
Class templates are the mechanism behind the entire STL containers library. Every std::vector, std::map, std::optional, and std::unique_ptr is a class template. Writing your own class templates lets you create type-safe, reusable data structures that integrate naturally with the rest of C++ generic code.
Learning Path
graph LR
A["42: Function Templates"] --> B["43: Class Templates"]
B --> C["44: Template Specialization"]
C --> D["45: Variadic Templates"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
Basic Class Template
A class template is declared with template <typename T> before the class definition.
#include <iostream>
#include <vector>
#include <stdexcept>
template <typename T>
class Stack {
private:
std::vector<T> elements_;
public:
void push(const T& value) {
elements_.push_back(value);
}
void pop() {
if (elements_.empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
elements_.pop_back();
}
T& top() {
if (elements_.empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elements_.back();
}
bool empty() const {
return elements_.empty();
}
size_t size() const {
return elements_.size();
}
};
int main() {
Stack<int> intStack;
intStack.push(10);
intStack.push(20);
std::cout << intStack.top() << "\n"; // 20
intStack.pop();
std::cout << intStack.top() << "\n"; // 10
Stack<std::string> stringStack;
stringStack.push("hello");
stringStack.push("world");
std::cout << stringStack.top() << "\n"; // world
}
Each instantiation (Stack<int>, Stack<std::string>) generates a separate class with its own set of member functions.
Non-Type Template Parameters
Class templates can also take compile-time values as parameters.
#include <iostream>
#include <stdexcept>
template <typename T, size_t Capacity>
class FixedArray {
private:
T data_[Capacity];
size_t size_ = 0;
public:
void push_back(const T& value) {
if (size_ >= Capacity) {
throw std::overflow_error("FixedArray full");
}
data_[size_++] = value;
}
T& operator[](size_t index) {
return data_[index];
}
const T& operator[](size_t index) const {
return data_[index];
}
size_t size() const { return size_; }
constexpr size_t capacity() const { return Capacity; }
T* begin() { return data_; }
T* end() { return data_ + size_; }
};
int main() {
FixedArray<int, 5> arr;
arr.push_back(10);
arr.push_back(20);
arr.push_back(30);
for (size_t i = 0; i < arr.size(); ++i) {
std::cout << arr[i] << " ";
}
std::cout << "\n"; // 10 20 30
std::cout << "Capacity: " << arr.capacity() << "\n"; // 5
}
Non-type parameters enable compile-time sizing without dynamic allocation, similar to std::array<T, N>.
Template Member Functions Outside the Class
Member function definitions outside the class body require the template parameter declaration.
#include <iostream>
template <typename T>
class Box {
private:
T value_;
public:
Box(const T& value);
T get() const;
void set(const T& value);
};
// Member function definitions outside class
template <typename T>
Box<T>::Box(const T& value) : value_(value) {}
template <typename T>
T Box<T>::get() const { return value_; }
template <typename T>
void Box<T>::set(const T& value) { value_ = value; }
// Member functions can themselves be templated
template <typename T>
class Converter {
public:
// Template member function inside class template
template <typename U>
T convert(const U& value) {
return static_cast<T>(value);
}
};
int main() {
Box<int> box(42);
std::cout << box.get() << "\n"; // 42
box.set(99);
std::cout << box.get() << "\n"; // 99
Converter<double> conv;
std::cout << conv.convert<int>(5) << "\n"; // 5.0
std::cout << conv.convert<float>(3.14f) << "\n"; // 3.14
}
When defining template member functions outside the class, repeat the full template <typename T> header.
Template Friends
Friend declarations inside class templates can grant friendship to specific instantiations or to all instantiations.
#include <iostream>
template <typename T>
class Point {
private:
T x_, y_;
public:
Point(T x, T y) : x_(x), y_(y) {}
// Each instantiation of Point is friends with operator<< for its own type
friend std::ostream& operator<<(std::ostream& os, const Point<T>& p) {
os << "(" << p.x_ << ", " << p.y_ << ")";
return os;
}
// Friend function template (all instantiations)
template <typename U>
friend Point<U> add(const Point<U>& a, const Point<U>& b);
};
template <typename T>
Point<T> add(const Point<T>& a, const Point<T>& b) {
return Point<T>(a.x_ + b.x_, a.y_ + b.y_);
}
int main() {
Point<int> p1(1, 2), p2(3, 4);
std::cout << p1 << " + " << p2 << " = " << add(p1, p2) << "\n";
// (1, 2) + (3, 4) = (4, 6)
}
Friend templates must be defined inline or have their template declaration visible before the class definition.
Inheritance with Class Templates
Class templates can inherit from other templates and from regular classes.
#include <iostream>
template <typename T>
class Base {
protected:
T value_;
public:
Base(T v) : value_(v) {}
virtual void print() const {
std::cout << "Base: " << value_ << "\n";
}
};
template <typename T>
class Derived : public Base<T> {
public:
Derived(T v) : Base<T>(v) {}
void print() const override {
std::cout << "Derived: " << this->value_ << "\n";
// Note: need 'this->' or Base<T>:: to access base members
}
};
int main() {
Derived<int> d(42);
d.print(); // Derived: 42
Base<int>* ptr = &d;
ptr->print(); // Derived: 42 (polymorphism works)
}
In derived templates, base class members are dependent names and must be accessed via this-> or Base<T>::.
Template Template Parameters
A template can accept another template as a parameter, enabling container adaptors.
#include <iostream>
#include <vector>
#include <list>
#include <deque>
// Container adaptor using template template parameter
template <typename T, template <typename> class Container = std::vector>
class Adaptor {
private:
Container<T> data_;
public:
void push(const T& val) { data_.push_back(val); }
void pop() { data_.pop_back(); }
T& back() { return data_.back(); }
void print() {
for (const auto& elem : data_) {
std::cout << elem << " ";
}
std::cout << "\n";
}
};
int main() {
Adaptor<int, std::vector> vecAdaptor;
vecAdaptor.push(1);
vecAdaptor.push(2);
vecAdaptor.push(3);
vecAdaptor.print(); // 1 2 3
Adaptor<int, std::list> listAdaptor;
listAdaptor.push(10);
listAdaptor.push(20);
listAdaptor.print(); // 10 20
}
Template template parameters enable flexible container selection while maintaining type safety.
Common Mistakes
Mistake 1: Forgetting template header for out-of-class definitions
template <typename T>
class MyClass {
void func();
};
// Missing template header
void MyClass::func() {} // Error!
Must write: template <typename T> void MyClass<T>::func() {}
Mistake 2: Dependent base class access
template <typename T>
class Derived : public Base<T> {
void func() { value_ = 5; } // Error: value_ not visible
};
Use this->value_ or Base<T>::value_ because value_ is a dependent name.
Mistake 3: Static members duplicated per instantiation
template <typename T>
struct Counter {
static int count;
};
Counter<int>::count; // One variable
Counter<double>::count; // Separate variable
Each template instantiation gets its own static member.
Mistake 4: Nested template syntax confusion
std::vector<std::vector<int>> matrix; // OK in C++11 (>> is fine)
// In C++98: std::vector<std::vector<int> > must have space
Mistake 5: Forgetting typename for dependent types
template <typename T>
void example() {
T::iterator it; // Error: need typename
typename T::iterator it; // OK
}
Practice Questions
- What is the output?
template <typename T>
struct Wrapper {
T value;
Wrapper(T v) : value(v) {}
};
int main() {
Wrapper<int> w(42);
std::cout << w.value;
}
Answer: 42 — the template instantiates with T = int.
Can a class template have a virtual function? Answer: Yes. Each instantiation generates a virtual function table independently.
What is a dependent name and why does it matter? Answer: A name whose meaning depends on a template parameter. The compiler cannot look it up until instantiation, so you must use
typenameortemplatekeywords.Write a class template
Pair<T1, T2>with first and second members.
template <typename T1, typename T2>
struct Pair { T1 first; T2 second; };
- How do you prevent implicit instantiation of a class template?
Answer: Use
extern template class MyClass<int>;(explicit instantiation declaration) in headers.
FAQ
Mini Project
Build a type-safe RingBuffer<T, Capacity> class template that implements a circular buffer with push, pop, front, back, size, and empty operations:
#include <iostream>
#include <array>
// Your RingBuffer<T, Capacity> template here
int main() {
RingBuffer<int, 3> buffer;
buffer.push(1);
buffer.push(2);
buffer.push(3);
buffer.push(4); // overwrites 1
std::cout << buffer.front() << "\n"; // 2
buffer.pop();
std::cout << buffer.front() << "\n"; // 3
std::cout << buffer.size() << "\n"; // 2
std::cout << buffer.empty() << "\n"; // 0 (false)
}
This project combines class templates with non-type parameters and manual memory management, giving you a low-level understanding of how containers like std::queue work internally.
What's Next
You now know how to create type-parameterized classes. Next, you will learn template specialization — how to provide custom implementations for specific types. This is how C++ optimizes std::vector<bool> differently from other vectors.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro