Templates: Writing Code Once for Any Type

What Are Templates?

Imagine a cookie cutter - you can use the same shape to make cookies from different doughs: chocolate, vanilla, or gingerbread. Templates are like cookie cutters for code - one pattern that works with many types!

Function Templates: Generic Functions

Function templates let you write one function that works with any type - like a universal adapter!

graph TD A[Function Templates] --> B[Type Parameter] A --> C[Function Body] A --> D[Type Deduction] B --> E["template<typename T>"] C --> F[Uses T as a type] D --> G[Compiler figures out T] H[Example Call] --> I["max(5, 3)"] I --> J[T = int] H --> K["max(3.14, 2.71)"] K --> L[T = double]

Function Template Examples

// Simple function template
template
T max(T a, T b) {
    return (a > b) ? a : b;
}

// Multiple type parameters
template
auto add(T a, U b) -> decltype(a + b) {
    return a + b;
}

// Template with arrays
template
T sum(T (&arr)[SIZE]) {
    T total = 0;
    for (int i = 0; i < SIZE; i++) {
        total += arr[i];
    }
    return total;
}

// Using the templates
int main() {
    // Type deduction
    cout << max(10, 20) << endl;           // T = int
    cout << max(3.14, 2.71) << endl;       // T = double
    cout << max('a', 'z') << endl;         // T = char
    
    // Explicit type specification
    cout << max(10, 3.14) << endl; // Forces double
    
    // Mixed types
    cout << add(5, 3.14) << endl;          // int + double
    
    // Array template
    int numbers[] = {1, 2, 3, 4, 5};
    cout << "Sum: " << sum(numbers) << endl; // SIZE deduced as 5
    
    return 0;
}

Template Specialization

Sometimes you need a special recipe for a specific ingredient. Template specialization lets you provide custom implementations for specific types!

Template Specialization Generic Template template<typename T> Works for most types Specialization template<> ...<char*> For C-strings Specialization template<> ...<bool> For booleans Partial Spec template<typename T> ...<T*> For all pointers Compiler chooses most specific version

Template Specialization Example

// Generic template
template
class Storage {
private:
    T value;
    
public:
    Storage(T val) : value(val) {}
    
    void print() {
        cout << "Generic storage: " << value << endl;
    }
    
    T getValue() { return value; }
};

// Full specialization for bool
template<>
class Storage {
private:
    bool value;
    
public:
    Storage(bool val) : value(val) {}
    
    void print() {
        cout << "Bool storage: " << (value ? "TRUE" : "FALSE") << endl;
    }
    
    bool getValue() { return value; }
};

// Partial specialization for pointers
template
class Storage {
private:
    T* ptr;
    
public:
    Storage(T* p) : ptr(p) {}
    
    void print() {
        if (ptr) {
            cout << "Pointer storage: *ptr = " << *ptr << endl;
        } else {
            cout << "Pointer storage: NULL" << endl;
        }
    }
    
    T* getValue() { return ptr; }
    
    ~Storage() {
        // Note: Doesn't delete - just stores pointer
    }
};

Class Templates: Generic Classes

Class templates are like blueprints for blueprints - they let you create classes that work with any type!

Class Template Implementation

template
class DynamicArray {
private:
    T* data;
    int size;
    int capacity;
    
    void resize() {
        capacity *= 2;
        T* newData = new T[capacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        delete[] data;
        data = newData;
    }
    
public:
    // Constructor
    DynamicArray(int initialCapacity = 10) 
        : size(0), capacity(initialCapacity) {
        data = new T[capacity];
    }
    
    // Destructor
    ~DynamicArray() {
        delete[] data;
    }
    
    // Copy constructor
    DynamicArray(const DynamicArray& other) 
        : size(other.size), capacity(other.capacity) {
        data = new T[capacity];
        for (int i = 0; i < size; i++) {
            data[i] = other.data[i];
        }
    }
    
    // Add element
    void push_back(const T& value) {
        if (size == capacity) {
            resize();
        }
        data[size++] = value;
    }
    
    // Access elements
    T& operator[](int index) {
        if (index < 0 || index >= size) {
            throw out_of_range("Index out of bounds");
        }
        return data[index];
    }
    
    // Get size
    int getSize() const { return size; }
    
    // Iterator support
    T* begin() { return data; }
    T* end() { return data + size; }
};

// Usage
int main() {
    // Array of integers
    DynamicArray numbers;
    numbers.push_back(10);
    numbers.push_back(20);
    numbers.push_back(30);
    
    // Array of strings
    DynamicArray words;
    words.push_back("Hello");
    words.push_back("Template");
    words.push_back("World");
    
    // Using range-based for loop
    for (int num : numbers) {
        cout << num << " ";
    }
    
    return 0;
}

Template Parameters: Beyond Types

Templates can take more than just types - they can take values too! It's like a recipe that needs both ingredients AND quantities.

graph TD A[Template Parameters] --> B[Type Parameters] A --> C[Non-Type Parameters] A --> D[Template Template Parameters] B --> E["typename T"] C --> F["int SIZE"] C --> G["char DELIMITER"] D --> H["template<typename> class Container"] I[Example] --> J["Array<int, 100>"] J --> K[Type: int
Size: 100]

Non-Type Template Parameters

// Fixed-size array template
template
class FixedArray {
private:
    T data[SIZE];  // Stack-allocated array
    
public:
    FixedArray() {
        // Initialize all elements
        for (int i = 0; i < SIZE; i++) {
            data[i] = T();
        }
    }
    
    T& operator[](int index) {
        if (index < 0 || index >= SIZE) {
            throw out_of_range("Index out of bounds");
        }
        return data[index];
    }
    
    constexpr int size() const { return SIZE; }
    
    void fill(const T& value) {
        for (int i = 0; i < SIZE; i++) {
            data[i] = value;
        }
    }
};

// Matrix template with dimensions
template
class Matrix {
private:
    T data[ROWS][COLS];
    
public:
    Matrix() {
        for (int i = 0; i < ROWS; i++) {
            for (int j = 0; j < COLS; j++) {
                data[i][j] = T();
            }
        }
    }
    
    T& at(int row, int col) {
        return data[row][col];
    }
    
    Matrix transpose() {
        Matrix result;
        for (int i = 0; i < ROWS; i++) {
            for (int j = 0; j < COLS; j++) {
                result.at(j, i) = data[i][j];
            }
        }
        return result;
    }
};

// Usage
FixedArray smallArray;
FixedArray largeArray;

Matrix rotation;
Matrix data;

Variadic Templates: Variable Arguments

Variadic templates are like a recipe that can handle any number of ingredients - from a simple sandwich to a full feast!

Variadic Template Examples

// Print function for any number of arguments
template
void print(T&& t) {
    cout << t << endl;
}

template
void print(T&& t, Args&&... args) {
    cout << t << " ";
    print(args...);  // Recursive call with remaining args
}

// Type-safe printf
template
void safePrintf(const string& format, Args... args) {
    printf(format.c_str(), args...);
}

// Sum any number of values
template
T sum(T t) {
    return t;
}

template
T sum(T first, Args... rest) {
    return first + sum(rest...);
}

// Create tuple-like structure
template
class Tuple {
    // Implementation details...
};

// Factory function
template
unique_ptr make_unique(Args&&... args) {
    return unique_ptr(new T(forward(args)...));
}

// Usage
print(1, 2.5, "Hello", 'A');  // Works with any types
cout << sum(1, 2, 3, 4, 5) << endl;  // 15

auto ptr = make_unique("Hello, World!");

Template Metaprogramming: Compile-Time Magic

Template metaprogramming is like having a chef (compiler) prepare ingredients (compute values) before the restaurant (program) even opens!

Compile-Time Computation Compile Time Templates expand Values computed Code generated Zero runtime cost! Runtime Uses computed values No calculation needed Fast execution Factorial at Compile Time Factorial<5>::value Compiler calculates: 120 Runtime sees: const int = 120

Template Metaprogramming Examples

// Compile-time factorial
template
struct Factorial {
    static constexpr int value = N * Factorial::value;
};

// Base case specialization
template<>
struct Factorial<0> {
    static constexpr int value = 1;
};

// Compile-time Fibonacci
template
struct Fibonacci {
    static constexpr int value = 
        Fibonacci::value + Fibonacci::value;
};

template<>
struct Fibonacci<0> {
    static constexpr int value = 0;
};

template<>
struct Fibonacci<1> {
    static constexpr int value = 1;
};

// Type traits
template
struct is_pointer {
    static constexpr bool value = false;
};

template
struct is_pointer {
    static constexpr bool value = true;
};

// Conditional type selection
template
struct conditional {
    using type = TrueType;
};

template
struct conditional {
    using type = FalseType;
};

// Usage
constexpr int fact5 = Factorial<5>::value;  // 120 at compile time
constexpr int fib10 = Fibonacci<10>::value; // 55 at compile time

cout << "5! = " << fact5 << endl;
cout << "Is int* a pointer? " << is_pointer::value << endl;

// Choose type based on condition
using MyType = conditional::type;

SFINAE: Substitution Failure Is Not An Error

SFINAE is like a restaurant menu - if they don't have what you ordered, they don't shut down, they just say "we don't serve that"!

graph TD A[SFINAE Principle] --> B[Template Substitution] B --> C{Valid?} C -->|Yes| D[Use this version] C -->|No| E[Try next version] E --> F[No error if alternatives exist] G[Example] --> H["enable_if"] H --> I[Enable function only for certain types]

Practice Exercise: Generic Container

Build a Generic Stack

Create a template-based stack with the following features:

  1. Works with any type
  2. Dynamic sizing
  3. Exception safety
  4. Iterator support
  5. Specialization for bool to save space
template
class Stack {
private:
    // TODO: Add member variables
    
public:
    Stack();
    ~Stack();
    
    void push(const T& value);
    void pop();
    T& top();
    const T& top() const;
    
    bool empty() const;
    size_t size() const;
    
    // TODO: Add iterator support
    class iterator {
        // Iterator implementation
    };
    
    iterator begin();
    iterator end();
};

// TODO: Specialize for bool
template<>
class Stack {
    // Bit-packed implementation
};
Implementation Hints

Modern C++ Template Features

Modern Template Features

// C++11: Auto return type
template
auto multiply(T t, U u) -> decltype(t * u) {
    return t * u;
}

// C++14: Generic lambdas
auto genericLambda = [](auto x, auto y) {
    return x + y;
};

// C++17: Class template argument deduction
pair p(1, 2.5);  // Deduces pair
vector v{1, 2, 3, 4};  // Deduces vector

// C++17: Fold expressions
template
auto sum(Args... args) {
    return (args + ...);  // Fold expression
}

// C++20: Concepts
template
concept Numeric = requires(T a, T b) {
    { a + b } -> convertible_to;
    { a - b } -> convertible_to;
    { a * b } -> convertible_to;
    { a / b } -> convertible_to;
};

template
T calculate(T a, T b) {
    return (a + b) * (a - b);
}

// Constexpr if (C++17)
template
string toString(T value) {
    if constexpr (is_same_v) {
        return value;
    } else if constexpr (is_arithmetic_v) {
        return to_string(value);
    } else {
        return "Unknown type";
    }
}

Template Best Practices

graph TD A[Template Best Practices] --> B[Keep it Simple] A --> C[Clear Error Messages] A --> D[Document Requirements] A --> E[Test with Multiple Types] B --> F[Don't over-engineer] C --> G[Use static_assert] C --> H[Use concepts C++20] D --> I[Comment type requirements] E --> J[Test edge cases]

Challenge Exercise: Expression Templates

Advanced Template Challenge

Build a simple expression template system for vector operations:

  1. Lazy evaluation of expressions
  2. Avoid temporary objects
  3. Support +, -, * operations
  4. Efficient computation
Basic Structure
template
class VecExpression {
public:
    double operator[](size_t i) const {
        return static_cast(*this)[i];
    }
    
    size_t size() const {
        return static_cast(*this).size();
    }
};

class Vec : public VecExpression {
    // Vector implementation
};

template
class VecSum : public VecExpression> {
    // Sum expression
};

Key Takeaways

graph LR A[Master Templates] --> B[Write Generic Code] B --> C[Maximize Reusability] C --> D[Build Efficient Libraries] D --> E[C++ Template Expert!]