Skip to content

Class Templates — Stack, Queue, Template Member Functions, Friends

DodaTech Updated 2026-06-28 8 min read

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

  1. 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.

  1. Can a class template have a virtual function? Answer: Yes. Each instantiation generates a virtual function table independently.

  2. 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 typename or template keywords.

  3. Write a class template Pair<T1, T2> with first and second members.

template <typename T1, typename T2>
struct Pair { T1 first; T2 second; };
  1. How do you prevent implicit instantiation of a class template? Answer: Use extern template class MyClass<int>; (explicit instantiation declaration) in headers.

FAQ

What is a class template in C++

A class template is a blueprint for generating type-specific classes. The compiler creates concrete classes when you provide template arguments like vector<int>.

Can class template member functions be defined in a .cpp file

Yes, but only if you explicitly instantiate the template for specific types. Otherwise, keep definitions in headers.

How do template static members work

Each instantiation of a class template gets its own copy of static data members. Counter<int>::count and Counter<double>::count are separate variables.

What is the difference between a class template and a template class

A class template is the template definition itself. A template class is a specific instantiation like Stack<int>. The distinction is rarely emphasized in practice.

Can I use 'template template parameters' in C++

Yes. A template template parameter lets a template accept another template as its argument, enabling flexible container adaptors and policies.

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