Chapter 28: Class Templates

Introduction

Class templates allow you to define a generic blueprint from which you can create specific instances of classes with different data types.

Basic Syntax

Defining a Class Template

template <typename T> // added
class Array
{
private:
    T* m_array;
    int m_size;

public:
    Array(int size)
        : m_size(size)
    {
        m_array = new T[m_size];
    }

    ~Array()
    {
        delete[] m_array;
    }

    T& operator[](int index)
    {
        return m_array[index];
    }

    int getSize() const
    {
        return m_size;
    }
};

Member Functions Defined Outside the Class

When defining member functions outside the class, you need to repeat the template declaration.

template <typename T>
T& Array<T>::operator[](int index)
{
    return m_array[index];
}

Template Implementation Methods

When template class is in a header file and the definition of member functions is in a .cpp file:

  1. Include .cpp in main: Not recommended.
  2. Put all template functions in .h: Preferred way.
  3. Move .cpp to .ini (inline) and add it to the bottom of .h: Use header guards.
  4. Create a fourth file (.h) that includes .cpp and .h and then include that file in main: Another way to organize.

Non-Type Template Parameters

A non-type template parameter is a template parameter where the type is predefined and is substituted for a constexpr value passed as an argument.

template <typename T, int size> // size is an integral non-type parameter
class StaticArray
{
private:
    T m_array[size];

public:
    T& operator[](int index)
    {
        return m_array[index];
    }

    int getSize() const
    {
        return size;
    }
};

Template Specialization

Full Specialization

Full specialization is a way to provide a specific implementation for a particular type.

// Primary template
template <typename T>
void print(const T& t)
{
    std::cout << t << '\n';
}

// Full specialization for type double
template<>  // template parameter declaration containing no template parameters
void print<double>(const double& d) // specialized for type double
{
    std::cout << std::scientific << d << '\n';
}

Full specializations are not implicitly inline. If you put a full specialization in a header file, it should be marked as inline to avoid ODR violations when included in multiple translation units.

Class Template Specialization

Class template specializations are treated as completely independent classes.

template <>
class Storage8<bool>
{
private:
    unsigned char m_data;

public:
    void set(int index, bool value)
    {
        if (value)
            m_data |= (1 << index);
        else
            m_data &= ~(1 << index);
    }

    bool get(int index) const
    {
        return (m_data & (1 << index)) != 0;
    }
};

Function Specialization

If you explicitly define some function for a specific type (instead of a template class), the compiler will deduce the template class that is needed for this function.

template<>
void Storage<double>::print() // not inline
{
    std::cout << std::scientific << m_value << '\n';
}

Partial Template Specialization

Partial template specialization allows you to specialize part of a template.

Example

template <int size> // size is still a template non-type parameter
void print(const StaticArray<char, size>& array) // we're explicitly defining type char here
{
    for (int count{ 0 }; count < size; ++count)
        std::cout << array[count];
}

// Partial specialization of a class template
template <typename T>
class Storage<T*> // This is partially specialized for T*
{
private:
    T* m_value;

public:
    Storage(T* value)
        : m_value(value)
    {
    }

    ~Storage()
    {
        delete m_value;
    }

    void print() const
    {
        std::cout << *m_value << '\n';
    }
};

Functions cannot be partially specialized.

Conclusion

Class templates provide a powerful way to create generic classes that can work with any data type. Understanding how to define templates, specialize them, and use non-type template parameters is crucial for writing flexible and reusable code.