C++ Templates Implementation Guide

Generic Programming Concepts

How can we create a universal swap function that works with different data types?

void Exchange(int& first, int& second)
{
    int temporary = first;
    first = second;
    second = temporary;
}

void Exchange(double& first, double& second)
{
    double temporary = first;
    first = second;
    second = temporary;
}

void Exchange(char& first, char& second)
{
    char temporary = first;
    first = second;
    second = temporary;
}

While function overloading provides a solution, it presents several limtiations:

  • Overloaded functions differ only in type, resulting in low code reuse efficiency. Each new type requires creating a corresponding function.
  • Maintainability issues arise since an error in one function might affect all overloaded versions.

The solution lies in providing the compiler with a blueprint to generate type-specific code automatically. C++ templates serve this purpose by allowing code generation based on type parameters.

Generic programming involves writing type-independent code as a method for code reuse, with templates forming the foundation of this approach.

Template Categories

C++ templates are primarily divided into function templates and class templates.

Function Templates

Function Template Definition

Function templates represent families of functions that operate independently of types. They become parameterized during usage, generating specific type versions based on argument types.

Template Syntax

template<typename Type1, typename Type2, ..., typename TypeN>

return_type function_name(parameter_list){}

#include <iostream>

template<typename ElementType>
void Exchange(ElementType& x, ElementType& y)
{
    ElementType temp = x;
    x = y;
    y = temp;
}

int main()
{
    int num1 = 15;
    int num2 = 25;
    std::cout << "Before exchange: " << num1 << " " << num2 << std::endl;
    Exchange(num1, num2);
    std::cout << "After exchange: " << num1 << " " << num2 << std::endl;
    return 0;
}

Note: The typename keyword defines template parameters and can be replaced with class (struct cannot substitute for class).

Template Mechanism

Function templates serve as blueprints rather than actual functions. The compiler generates specific type functions based on usage patterns, automating repetitive coding tasks.

During compilation, the compiler deduces template parameters from argument types and creates corresponding specialized functions. For example, when using double types with a template, the compiler identifies T as double and generates double-specific code.

Template Instantiation

Using template functions with different parameter types constitutes template instantiation, which occurs through implicit or explicit instantiation.

Implicit Instantiation

template<typename DataType>
void Exchange(const DataType& elem1, const DataType& elem2)
{
    DataType temp = elem1;
    elem1 = elem2;
    elem2 = temp;
}

int main()
{
    int value1 = 15;
    double value2 = 3.14;
    
    Exchange(value1, (int)value2);  // Type casting for compatibility
    Exchange((double)value1, value2);
    return 0;
}

The compiler deduces types from template function arguments. Mismatched parameter types require explicit conversion.

Explicit Instantiation

template<typename GenericType>
void Exchange(const GenericType& item1, const GenericType& item2)
{
    GenericType temporary = item1;
    item1 = item2;
    item2 = temporary;
}

int main()
{
    int integer_val = 15;
    double double_val = 3.14;
    
    Exchange<int>(integer_val, double_val);
    Exchange<double>(integer_val, double_val);
    return 0;
}

When types don't match, the compiler attempts implicit conversion, reporting errors if conversion fails.

Template Parameter Matching Rules

A non-template function can coexist with a同名 function template, and the template can instantiate the non-template function.

// Specialized integer addition
int Sum(int x, int y)
{
    return x + y;
}

// Generic addition template
template<class TemplateType>
TemplateType Sum(TemplateType x, TemplateType y)
{
    return x + y;
}

void Demo()
{
    Sum(5, 10);           // Matches non-template function
    Sum<int>(5, 10);      // Calls template instantiation
}

When both non-template and template functions are available with identical conditions, the non-template function takes precedence. The template generates better matches when appropriate.

// Specialized integer addition
int Sum(int x, int y)
{
    return x + y;
}

// Generic addition with different types
template<class Type1, class Type2>
Type1 Sum(Type1 x, Type2 y)
{
    return x + y;
}

void Demo()
{
    Sum(5, 10);           // Exact match with non-template
    Sum(5, 10.5);         // Template generates better match
}

Template functions prohibit automatic type conversion, while regular functions permit it.

Class Templates

Class Template Definition

template<class T1, class T2, ..., class Tn>
class ClassTemplateName
{
    // Class member definitions
};
// Dynamic array template
template<class ContentType>
class DynamicArray
{
public:
    DynamicArray(size_t initial_capacity = 10)
        : data(new ContentType[initial_capacity])
        , current_size(0)
        , total_capacity(initial_capacity)
    {}
    
    ~DynamicArray();
    
    void Append(const ContentType& element);
    void RemoveLast();
    
    size_t GetSize() { return current_size; }
    
    ContentType& operator[](size_t position)
    {
        assert(position < current_size);
        return data[position];
    }
    
private:
    ContentType* data;
    size_t current_size;
    size_t total_capacity;
};

// External definition requires template parameters
template <class ContentType>
DynamicArray<ContentType>::~DynamicArray()
{
    if(data)
        delete[] data;
    current_size = total_capacity = 0;
}

Class Template Instantiation

Class template instantiation differs from function templates by requiring the template name followed by <> containing the specific type. The template name itself isn't a real class—the instantiated result constitutes the actual class.

// DynamicArray is template name, DynamicArray<int> is actual type
DynamicArray<int> integer_array;
DynamicArray<double> double_array;

typename vs class in Templates

Generally, typename and class are interchangeable in template declarations. However, typename becomes necessary when referring to dependent types within templates to inform the compiler about type identifiers.

Non-Type Template Parameters

Template Parameter Classification

Template parameters divide into type parameters and non-type parameters.

Type parameters appear in template parameter lists following class or typename.

Non-type parameters use constants as template parameters, treated as constants within templates.

namespace container
{
    template<class ArrayType, size_t ArraySize = 10>
    class FixedArray
    {
    public:
        ArrayType& operator[](size_t index) { return elements[index]; }
        const ArrayType& operator[](size_t index) const { return elements[index]; }
        
        size_t size() const { return current_size; }
        bool empty() const { return 0 == current_size; }
        
    private:
        ArrayType elements[ArraySize];
        size_t current_size;
    };
}

Important notes:

  • Floating-point numbers, class objects, and strings cannot serve as non-type template parameters—only integral types are permitted.
  • Non-type template parameters must be resolvable at compile time.

Template Specialization

Specialization Purpose

While templates typically create type-independent code, special types might produce incorrect results requiring specialized handling.

// Generic comparison template
template<class Comparable>
bool IsLess(Comparable a, Comparable b)
{
    return a < b;
}

int main()
{
    std::cout << IsLess(5, 10) << std::endl;  // Correct comparison
    
    Date date1(2023, 8, 15);
    Date date2(2023, 8, 16);
    std::cout << IsLess(date1, date2) << std::endl;  // Correct comparison
    
    Date* ptr1 = &date1;
    Date* ptr2 = &date2;
    std::cout << IsLess(ptr1, ptr2) << std::endl;  // Incorrect: compares addresses
    return 0;
}

Template specialization provides special implementations for specific types, available as function template specialization and class template specialization.

Full Specialization

template<class Param1, class Param2>
class DataContainer
{
public:
    DataContainer() { std::cout << "DataContainer<Param1, Param2>" << std::endl; }
private:
    Param1 member1;
    Param2 member2;
};

template<>
class DataContainer<int, char>
{
public:
    DataContainer() { std::cout << "DataContainer<int, char>" << std::endl; }
private:
    int member1;
    char member2;
};

void TestFunction()
{
    DataContainer<int, int> container1;
    DataContainer<int, char> container2;
}

Partial Specialization

Partial specialization imposes additional constraints on template parameters.

template<class T1, class T2>
class DataContainer
{
public:
    DataContainer() { std::cout << "DataContainer<T1, T2>" << std::endl; }
private:
    T1 member1;
    T2 member2;
};

Partial Parameter Specialization

Specializing only some template parameters:

// Specialize second parameter as int
template <class Type1>
class DataContainer<Type1, int>
{
public:
    DataContainer() { std::cout << "DataContainer<Type1, int>" << std::endl; }
private:
    Type1 member1;
    int member2;
};

Parameter Restriction Specialization

Imposing additional constraints on template parameters:

// Pointer specialization
template <typename P1, typename P2>
class DataContainer <P1*, P2*>
{ 
public:
    DataContainer() { std::cout << "DataContainer<P1*, P2*>" << std::endl; }
private:
    P1 member1;
    P2 member2;
};

// Reference specialization
template <typename R1, typename R2>
class DataContainer <R1&, R2&>
{
public:
    DataContainer(const R1& d1, const R2& d2)
        : member1(d1)
        , member2(d2)
    {
        std::cout << "DataContainer<R1&, R2&>" << std::endl;
    }
private:
    const R1 & member1;
    const R2 & member2;
};

void TestSpecialization()
{
    DataContainer<double, int> cont1;      // Calls int specialization
    DataContainer<int, double> cont2;      // Calls base template
    DataContainer<int*, int*> cont3;       // Calls pointer specialization
    DataContainer<int&, int&> cont4(5, 10); // Calls reference specialization
}

Specialization Requirements

Function template specialization requires:

  • Existing base function template
  • Empty angle brackets following template keyword
  • Function name followed by angle brackets specifying specialized types
  • Parameter list identical to base template function

Template Separate Compilation

Separate Compilation Concept

Separate compilation involves multiple source files compiling individually into object files, then linking into a single executable.

Template Separate Compilation Issues

When template declarations and definitions separate across files:

// header.h
template<class T>
T Add(const T& x, const T& y);

// implementation.cpp
template<class T>
T Add(const T& x, const T& y)
{
    return x + y;
}

// main.cpp
#include "header.h"
int main()
{
    Add(5, 10);
    Add(5.5, 10.5);
    return 0;
}

This approach fails because templates don't instantiate without type information, preventing symbol table generation.

Solutions:

  • Place declarations and definitions in the same file (.hpp or .h recommended)
  • Explicit instantiation at definition location (not recommended)

Template Summary

Advantages:

  • Code reuse and resource conservation through template libraries like STL
  • Enhanced code flexibility

Disadvantages:

  • Code bloat and increased compilation time
  • Complex and confusing error messages

Tags: C++ Templates Generic Programming Template Specialization Class Templates Function Templates

Posted on Sun, 17 May 2026 03:00:55 +0000 by shorty114