Class Templates and Non Type Template Parameters
Templates and Generic Programming

5.2 Class Templates and Non Type Template Parameters

Just as you can write function templates, you can write class templates to create generic data structures. We can also pass actual values (like integers or pointers) as template arguments. These are called non type template parameters.

Let's look at class templates and how value parameters enable compile time collection sizes.

Class Templates: Generic Containers

A class template allows you to define a structure where the type of member variables is determined at compile time. This is how containers like vectors and lists are built. Let's look at a custom wrapper class:

template <typename T>
class Wrapper {
    T value;
public:
    Wrapper(T val) : value{val} {}
    T getValue() const { return value; }
};
A simple generic class wrapper.

Non Type Template Parameters: Compile Time Values

What if you want to specify the size of an array at compile time using templates? You can pass values (like integers) directly inside the angle brackets. The syntax is:

template <typename T, unsigned int Size>
class StaticArray {
    T data[Size]; // Raw C style array sized at compile time!
public:
    unsigned int getSize() const { return Size; }
};
Custom array class template with size value parameter.

Because Size is a template parameter, the compiler instantiates a completely unique class type for each size. For example, StaticArray<int, 5> and StaticArray<int, 10> are treated as two entirely different, incompatible classes. You cannot assign one to the other.

The std::array Collection

The standard library provides std::array as a safer, zero cost wrapper around raw arrays. It uses non type template parameters to reserve space directly on the stack, preventing dynamic heap allocation latency:

#include <iostream>
#include <array>

int main() {
    // Stack allocated array of 5 integers, zero heap overhead!
    std::array<int, 5> numbers{10, 20, 30, 40, 50};

    std::cout << "Size: " << numbers.size() << '\n';
    std::cout << "Element at index 2: " << numbers.at(2) << '\n'; // Boundary checks
    return 0;
}
Using std::array with template size constants.

* The Sizing Metaphor: Non type template parameters are like baking templates. Instead of buying a baking tray that can resize itself at runtime (like a slow dynamic vector), you buy a rigid 8 inch steel tray (a stack allocated template array). It is incredibly sturdy and requires zero assembly, but you cannot change its size once it is placed in the oven.

Class Template Argument Deduction (CTAD)

In older C++ versions, you had to explicitly write all type parameters when instantiating a class template (e.g. Wrapper<int> w{5}). Since C++17, the compiler can automatically deduce the template parameters from the arguments passed to the constructor. This is called Class Template Argument Deduction (CTAD):

#include <utility>

int main() {
    // Old style (Verbose)
    std::pair<int, double> oldPair{42, 3.14};

    // Modern C++ style (CTAD deduces <int, double> automatically!)
    std::pair modernPair{42, 3.14};
    return 0;
}
Instantiating class templates using CTAD.
Finished reading this lesson?