What Is a Class Template?
A class template lets you describe one class that works with different types. For example, a stack can hold numbers, strings, or other objects while providing the same operations.
The template parameter T specifies the element type:
#include <cassert>
#include <vector>
template<typename T>
class Stack
{
private:
std::vector<T> elems;
public:
void push(const T& elem);
void pop();
const T& top() const;
bool empty() const
{
return elems.empty();
}
};
Here, std::vector<T> stores elements of type T. The push() method accepts an element of that type, and top() returns a reference to the top element.
For a type parameter, template<typename T> and template<class T> are equivalent. The examples below use the first form.
Defining Member Functions
You can define a member function inside the class, as we did with empty(). To define one outside the class, repeat the template parameter declaration and qualify the function name with Stack<T>:::
template<typename T>
void Stack<T>::push(const T& elem)
{
elems.push_back(elem);
}
template<typename T>
void Stack<T>::pop()
{
assert(!elems.empty());
elems.pop_back();
}
template<typename T>
const T& Stack<T>::top() const
{
assert(!elems.empty());
return elems.back();
}
These definitions, together with the class definition, form the complete implementation of Stack<T>. The compiler needs access to template member definitions when it generates them for a particular type. That is why template implementations are usually placed in header files.
Using a Class Template
When creating an object, provide a concrete type in place of T:
Stack<int> intStack;
Stack<std::string> stringStack;
In Stack<int>, the elems member has type std::vector<int>, and push() accepts a const int&. In Stack<std::string>, the corresponding types are std::vector<std::string> and const std::string&.
Example
Once Stack<T> is defined, you can create stacks with different element types:
#include <iostream>
#include <string>
int main()
{
Stack<int> intStack;
intStack.push(7);
intStack.push(42);
std::cout << intStack.top() << '\n'; // 42
Stack<std::string> stringStack;
stringStack.push("hello");
stringStack.push("world");
std::cout << stringStack.top() << '\n'; // world
}
Stack<int> and Stack<std::string> are different types. Each set of template arguments produces a distinct class template specialization.
Stack and Stack<T> Inside the Class
Inside a class template definition, Stack without <T> refers to the current specialization. This is useful when declaring copy operations:
template<typename T>
class Stack
{
public:
Stack(const Stack& other);
Stack& operator=(const Stack& other);
};
For Stack<int>, the constructor parameter means const Stack<int>&. You can also write the full form, Stack<T>, inside the class, but the shorter form is usually easier to read.
Outside the class, you must specify the template arguments explicitly:
template<typename T>
bool operator==(const Stack<T>& lhs,
const Stack<T>& rhs);
This use of the class name without template arguments inside its own definition is called an injected-class-name.
When Code Is Generated for a Concrete Type
When you use Stack<int>, the compiler substitutes int for T. The stack therefore stores a std::vector<int>, and its member functions work with int.
Individual member functions are generally instantiated when their definitions are needed. The following code uses push() and top():
Stack<int> stack;
stack.push(42);
std::cout << stack.top();
If pop() is never needed for Stack<int>, its body generally does not need to be instantiated. As a result, one member function can impose requirements on T that other members do not have.
A Requirement of One Member Function
#include <iostream>
template<typename T>
class Data
{
public:
void print(const T& value)
{
std::cout << value;
}
};
When print() is used, type T must support stream output through operator<<. Other members of the class may not need that operation.
Types as Template Arguments
You can use a specialization like any other type: create objects, arrays, references, and aliases.
using IntStack = Stack<int>;
IntStack stack;
Stack<int> stacks[10];
Stack<int>& ref = stack;
const Stack<int>& constRef = stack;
A template argument can itself be a compound type. For example, Stack<float*> stores pointers, while Stack<Stack<int>> stores stacks of integers.
A Stack of Stacks
Stack<int> first;
first.push(10);
first.push(20);
Stack<Stack<int>> stacks;
stacks.push(first);
std::cout << stacks.top().top() << '\n'; // 20
For the outer Stack, the parameter T is Stack<int>. Its underlying container is therefore a std::vector<Stack<int>>.
Since C++11, closing angle brackets in nested templates can appear together: Stack<Stack<int>>. Earlier C++ standards required a space between them: Stack<Stack<int> >.
Where You Can Declare a Class Template
An ordinary class template can be declared at namespace scope:
template<typename T>
class Data
{
};
A class template can also be a member of another class. It cannot, however, be declared inside a function body: moving the declaration above into a function would cause a compilation error.
Class Template Argument Deduction
In the stack examples, the element type is specified explicitly: Stack<int> stack;. Since C++17, the compiler can sometimes deduce class template arguments from constructor arguments. This feature is called Class Template Argument Deduction, or CTAD.
Type Deduction in C++17
template<typename T>
class Value
{
public:
Value(T value)
: value_(value)
{
}
private:
T value_;
};
Value value(42);
From the argument 42, the compiler deduces T = int. The type of value is Value<int>.
The Stack shown earlier has no constructor arguments from which the element type can be deduced. Therefore, Stack stack; does not work for that template. You must specify the type, for example, Stack<int> stack;.
Key Points
- A class template describes a general implementation; template arguments determine a concrete type.
- To define a member function outside the class, repeat
template<typename T>and qualify its name withStack<T>::. - Template member definitions must be available to the compiler when those members are instantiated.
Stack<int>andStack<std::string>are different types.- Since C++17, class template arguments can sometimes be deduced from constructor arguments.