C/C++ Templates

Templates in C/C++ allow us to define generic functions and classes that work with any data type. They enable code reusability and type safety while maintaining high performance.

Why Use Templates?

Function Templates

A function template allows a function to operate on different data types without rewriting it.

Example: Function Template for Maximum Value

#include <iostream>
using namespace std;

// Template function to find maximum of two values
template <typename T>
T findMax(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << "Max of 10 and 20: " << findMax(10, 20) << endl;
    cout << "Max of 5.5 and 2.2: " << findMax(5.5, 2.2) << endl;
    return 0;
}

Class Templates

A class template allows us to define a blueprint for a class that can work with any data type.

Example: Class Template for a Simple Pair

#include <iostream>
using namespace std;

// Template class for a pair of values
template <typename T1, typename T2>
class Pair {
private:
    T1 first;
    T2 second;
public:
    Pair(T1 f, T2 s) : first(f), second(s) {}
    void display() {
        cout << "(" << first << ", " << second << ")" << endl;
    }
};

int main() {
    Pair<int, double> p1(10, 20.5);
    p1.display(); // Output: (10, 20.5)
    
    Pair<string, int> p2("Alice", 30);
    p2.display(); // Output: (Alice, 30)
    return 0;
}

Template Specialization

Sometimes, we need a specific implementation of a template for a certain type.

Example: Specialization for char*

#include <iostream>
#include <cstring>
using namespace std;

// General template
template <typename T>
T findMax(T a, T b) {
    return (a > b) ? a : b;
}

// Specialization for char*
template <>
const char* findMax<const char*>(const char* a, const char* b) {
    return (strcmp(a, b) > 0) ? a : b;
}

int main() {
    cout << "Max of Hello and World: " << findMax("Hello", "World") << endl;
    return 0;
}

Non-Type Template Parameters (Number Templates)

Templates can also accept non-type parameters, such as integers, at compile time. This is useful for fixed-size data structures and compile-time constants.

Example: Fixed-Size Array with a Number Template

#include <iostream>
using namespace std;

// Template with a non-type (number) parameter
template <typename T, int N>
class FixedArray {
private:
    T data[N];
public:
    FixedArray() {
        for (int i = 0; i < N; i++) data[i] = T{};
    }
    T& operator[](int index) { return data[index]; }
    int size() const { return N; }
};

int main() {
    FixedArray<int, 5> arr;
    arr[0] = 10;
    arr[1] = 20;
    cout << "Size: " << arr.size() << endl;   // Output: 5
    cout << "arr[0]: " << arr[0] << endl;     // Output: 10
    return 0;
}

The size N is a compile-time constant — the array lives on the stack and no dynamic allocation is needed.

Example: Power Function Template (typename base, constant int exponent)

A function template where the base type (T) is a typename and the exponent is a compile-time constant integer:

#include <iostream>
using namespace std;

// T  = type of the base (int, double, float, ...)
// Exp = exponent — must be known at compile time
template <typename T, int Exp>
T power(T base) {
    T result = 1;
    for (int i = 0; i < Exp; i++) {
        result *= base;
    }
    return result;
}

int main() {
    cout << power<int, 3>(2)      << endl; // 2^3  = 8
    cout << power<double, 4>(1.5) << endl; // 1.5^4 = 5.0625
    cout << power<float, 0>(99.9) << endl; // anything^0 = 1
    return 0;
}

Key points:

Example: Compile-Time Power (struct / template metaprogramming)

template <int Base, int Exp>
struct Power {
    static const int value = Base * Power<Base, Exp - 1>::value;
};

template <int Base>
struct Power<Base, 0> {
    static const int value = 1;
};

int main() {
    cout << Power<2, 10>::value << endl; // Output: 1024
    return 0;
}

The entire calculation happens at compile time — no runtime cost.

Best Practices for Templates

Conclusion

C++ templates provide a powerful way to write reusable and efficient code. Function templates and class templates help achieve type safety and code flexibility, while specialization allows fine-tuning for specific needs.