Class Templates & CTAD
Build generic data structures with class templates. Understand member definitions, default arguments, and how C++17's CTAD lets you omit template arguments.
Class Templates
A class template defines a family of classes parameterized by one or more types (or non-type values). The standard library is built on class templates: std::vector, std::map, std::optional, std::shared_ptr. When you write std::vector, the compiler instantiates the entire class with T = int, generating a concrete type.
Class templates follow the same rules as function templates: they must be defined in headers, and each unique set of template arguments produces a distinct type. Stack and Stack are completely unrelated types — they share no inheritance relationship.
A Generic Stack
Here is a simple generic stack. Note how member functions defined outside the class body require repeating the template parameter list.
#include <vector>
#include <stdexcept>
#include <iostream>
template <typename T>
class Stack {
private:
std::vector<T> elements_;
public:
void push(const T& value);
void pop();
const T& top() const;
bool empty() const { return elements_.empty(); }
std::size_t size() const { return elements_.size(); }
};
// Member definitions outside the class require the template header
template <typename T>
void Stack<T>::push(const T& value) {
elements_.push_back(value);
}
template <typename T>
void Stack<T>::pop() {
if (elements_.empty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
elements_.pop_back();
}
template <typename T>
const T& Stack<T>::top() const {
if (elements_.empty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elements_.back();
}
int main() {
Stack<int> intStack;
intStack.push(42);
intStack.push(17);
std::cout << intStack.top() << '\n'; // 17
intStack.pop();
std::cout << intStack.top() << '\n'; // 42
}Multiple Parameters & Defaults
Class templates can have multiple template parameters and default template arguments, just like function parameters can have default values. Default arguments are resolved left to right.
#include <vector>
#include <deque>
#include <iostream>
// Container defaults to std::deque<T> — matches std::stack's design
template <typename T, typename Container = std::deque<T>>
class Stack {
private:
Container elements_;
public:
void push(const T& value) { elements_.push_back(value); }
void pop() { elements_.pop_back(); }
const T& top() const { return elements_.back(); }
bool empty() const { return elements_.empty(); }
};
int main() {
Stack<int> s1; // uses deque<int>
Stack<int, std::vector<int>> s2; // uses vector<int>
s1.push(10);
s2.push(20);
std::cout << s1.top() << '\n'; // 10
std::cout << s2.top() << '\n'; // 20
}Class Template Argument Deduction (CTAD)
Before C++17, you always had to specify template arguments when constructing class template instances: std::pair. CTAD (Class Template Argument Deduction), introduced in C++17, lets the compiler deduce class template arguments from constructor arguments, just like function template argument deduction.
With CTAD: std::pair p(1, 3.14); — the compiler deduces std::pair. This works for std::vector, std::tuple, std::optional, std::array (C++20), and your own class templates.
CTAD uses deduction guides — either implicit (generated from constructors) or explicit (user-defined). The compiler tries each guide and picks the best match.
CTAD & Deduction Guides
User-defined deduction guides tell the compiler how to map constructor arguments to template arguments. They are especially useful when the constructor argument type does not directly correspond to the template parameter.
#include <iostream>
#include <cstddef>
template <typename T, std::size_t N>
struct Array {
T data[N];
};
// Without a deduction guide, CTAD cannot deduce T and N.
// This explicit deduction guide maps brace-init with N elements
// of the same type T to Array<T, N>.
template <typename First, typename... Rest>
Array(First, Rest...) -> Array<First, 1 + sizeof...(Rest)>;
// Standard library example: std::vector CTAD from iterators
#include <vector>
#include <list>
int main() {
// CTAD with our custom deduction guide
Array arr{1, 2, 3, 4, 5}; // deduces Array<int, 5>
std::cout << arr.data[2] << '\n'; // 3
// Standard library CTAD examples (C++17)
std::pair p(42, 3.14); // pair<int, double>
std::vector v{1, 2, 3}; // vector<int>
std::tuple t(1, 2.0, 'a'); // tuple<int, double, char>
// CTAD from iterator range
std::list<int> lst{10, 20, 30};
std::vector v2(lst.begin(), lst.end()); // vector<int>
}Member Templates
A class template's member functions can themselves be templates, adding additional template parameters. This is called a member template. A common use case is enabling assignment or construction from a related but different instantiation.
For example, you might want to assign a Stack from a Stack — but since these are unrelated types, the default copy assignment won't work. A member template assignment operator solves this:
``cpp``
template
class Stack {
public:
template
Stack& operator=(const Stack& other);
};
Inside this member template, U may differ from T. The body can copy elements from other as long as U is convertible to T. This pattern is used extensively in the standard library — std::shared_ptr converts to std::shared_ptr via a member template constructor.
When you specialize a class template, the specialization is a completely new class — it does not inherit any members from the primary template. You must redefine every member function and data member. Forgetting to redefine a member in the specialization results in a compile error when that member is used. Also, partial specialization is only available for class templates, not function templates. We cover specialization in detail in the next chapter.
- Class templates define families of classes — each unique argument set produces a distinct type
- Member functions defined outside the class must repeat the
templateheader and useClassName:: - Default template arguments work left-to-right, just like default function arguments
- CTAD (C++17) lets the compiler deduce class template arguments from constructor arguments
- Explicit deduction guides map constructor argument patterns to template arguments
- Member templates allow operations across different instantiations of the same class template
Quiz — Test Your Knowledge
(15 XP)1. Given `template <typename T, typename Container = std::deque<T>> class Stack`, what is the type of `Stack<int>`?
2. What is CTAD (C++17)?
3. When you specialize a class template, what happens to the members of the primary template?