Class Templates in C++
Class templates let one class definition work with many types while preserving
compile-time type checking. The main idea is simple: write the container or
algorithm once, then let the compiler generate a concrete version such as
Box<int>, Box<string>, or Array<double, 100> when the program uses it.
Use class templates when the behavior is the same but the stored type changes. Do not use them just to avoid thinking about design; if two types need different behavior, specialization or separate classes may be clearer.
Table of Contents
Basic Class Templates
template<typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T get() const { return value; }
void set(T v) { value = v; }
};
Box<int> intBox(42);
Box<string> strBox("Hello");
Box<double> dblBox(3.14);
Member Functions
Inside Class
template<typename T>
class Stack {
vector<T> data;
public:
void push(const T& value) {
data.push_back(value);
}
T pop() {
T top = data.back();
data.pop_back();
return top;
}
bool empty() const {
return data.empty();
}
};
Outside Class
template<typename T>
class MyClass {
T value;
public:
MyClass(T v);
T getValue() const;
void setValue(T v);
};
template<typename T>
MyClass<T>::MyClass(T v) : value(v) {}
template<typename T>
T MyClass<T>::getValue() const { return value; }
template<typename T>
void MyClass<T>::setValue(T v) { value = v; }
Non-Type Parameters
template<typename T, size_t Size>
class Array {
T data[Size];
public:
T& operator[](size_t i) { return data[i]; }
const T& operator[](size_t i) const { return data[i]; }
constexpr size_t size() const { return Size; }
};
Array<int, 10> arr;
arr[0] = 42;
// Size is compile-time constant
Array<double, 100> bigArr;
Default Template Arguments
template<typename T = int, size_t Size = 10>
class Buffer {
T data[Size];
public:
size_t size() const { return Size; }
};
Buffer<> buf1; // int, size 10
Buffer<double> buf2; // double, size 10
Buffer<char, 256> buf3; // char, size 256
Static Members
Each instantiation has its own static members:
template<typename T>
class Counter {
public:
static int count;
Counter() { ++count; }
~Counter() { --count; }
};
template<typename T>
int Counter<T>::count = 0;
Counter<int> a, b, c;
cout << Counter<int>::count; // 3
Counter<double> d;
cout << Counter<double>::count; // 1 (separate counter!)
Template Specialization
Full Specialization
// Primary template
template<typename T>
class Printer {
public:
void print(const T& value) {
cout << value << endl;
}
};
// Specialization for bool
template<>
class Printer<bool> {
public:
void print(const bool& value) {
cout << (value ? "true" : "false") << endl;
}
};
Partial Specialization
// Primary template
template<typename T>
class Container {
T value;
public:
void show() { cout << "Value: " << value << endl; }
};
// Partial specialization for pointers
template<typename T>
class Container<T*> {
T* ptr;
public:
void show() { cout << "Pointer to: " << *ptr << endl; }
};
Best Practices
✅ Do
// 1. Keep template code in headers
// myclass.h
template<typename T>
class MyClass {
// Full implementation here
};
// 2. Use typename for dependent types
template<typename T>
class Container {
typename T::value_type item; // T::value_type is a type
};
// 3. Provide type aliases
template<typename T>
class MyContainer {
public:
using value_type = T;
using size_type = size_t;
};
❌ Don't
// 1. Don't separate template implementation
// WON'T LINK:
// header.h: template<typename T> class C { void f(); };
// source.cpp: template<typename T> void C<T>::f() { }
// 2. Don't assume T has specific operations
template<typename T>
class Bad {
void sort() { /* assumes T is sortable */ }
};
Quick Reference
// Basic class template
template<typename T>
class MyClass { T data; };
// Multiple parameters
template<typename T, typename U>
class Pair { T first; U second; };
// Non-type parameter
template<typename T, size_t N>
class Array { T data[N]; };
// Default arguments
template<typename T = int>
class Default { };
// Member outside class
template<typename T>
void MyClass<T>::method() { }
// Full specialization
template<>
class MyClass<int> { };
// Partial specialization
template<typename T>
class MyClass<T*> { };
Compile & Run
Learner Notes
The most important rule is that template code usually needs to be visible at the
point where it is instantiated. That is why template class definitions often live
in header files instead of only in .cpp files. If the linker says it cannot
find a template member function, the implementation may be hidden from the
translation unit that needs it.
Read a class template in three passes. First, identify the template parameters:
typename T, non-type parameters such as size_t Size, and any defaults.
Second, inspect which members depend on those parameters. Third, check how the
class behaves for edge cases such as pointers, const objects, and empty
containers.
Practice by turning a normal Stack<int> into Stack<T>, then instantiate it
with int, string, and a custom struct. If all three versions compile and the
public API still makes sense, the template is probably shaped well.
g++ -std=c++17 -Wall examples.cpp -o examples && ./examples