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 with Stack<T>::.
  • Template member definitions must be available to the compiler when those members are instantiated.
  • Stack<int> and Stack<std::string> are different types.
  • Since C++17, class template arguments can sometimes be deduced from constructor arguments.