Skip to content

Template

If the same code for different types looks duplicated, we can use templates to reduce them.

void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

void swap(double& a, double& b) {
    double temp = a;
    a = b;
    b = temp;
}

This can be changed to:

template <typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

Templates can be used with functions:

template <typename T> // Declares a template with a type parameter T
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    int i = maximum(5, 10);      // T becomes int
    double d = maximum(3.14, 2.71); // T becomes double
    char c = maximum('a', 'z');   // T becomes char
    return 0;
}

And classes:

template <typename T1, typename T2> // Two type parameters
class Pair {
public:
    T1 first;
    T2 second;

    Pair(T1 f, T2 s) : first(f), second(s) {}

    void print() {
        std::cout << "First: " << first << ", Second: " << second << std::endl;
    }
};

int main() {
    Pair<int, double> p1(10, 20.5);      // T1 is int, T2 is double
    Pair<std::string, int> p2("Hello", 123); // T1 is std::string, T2 is int

    p1.print();
    p2.print();
    return 0;
}

And variables:

template <typename T>
constexpr int DefaultBufferSize = 1024 / sizeof(T);

// Usage:
// char buffer_char[DefaultBufferSize<char>];   // buffer_char has size 1024
// int buffer_int[DefaultBufferSize<int>];     // buffer_int has size 256 (if int is 4 bytes)

Type specific implementations

For certain types, you may want to have a specialized implementation instead of the general template one. You can use the explicit specialization here.

template <typename T>
void print(T value) {
    std::cout << "Generic print: " << value << std::endl;
}

template <> // No new template parameters; we're specializing an existing one
void print<const char*>(const char* value) { // Specify the exact type
    std::cout << "Specialized for C-string: " << value << std::endl;
}

// Usage:
// print(123);           // Calls generic print<int>
// print("Hello");       // Calls specialized print<const char*>
// print(std::string("World")); // Calls generic print<std::string>

Limit the types

To limit what types can be used with the template, you can simply use asserts.

template <typename T>
T multiply(T a, T b) {
    // Assert that T is an arithmetic type (e.g., int, float, double)
    static_assert(std::is_arithmetic_v<T>, "Type must be an arithmetic type!");
    return a * b;
}

Or for C++20 and later, you can use concepts.

// Define a concept that requires a type to be integral
template <typename T>
concept Integral = std::is_integral_v<T>;

// Use the concept to constrain the template function
template <Integral T>
T add(T a, T b) {
    return a + b;
}

Compile

They should be defined in header files. They need to be instantiated at compile time since the compiler needs to know the function/class when it sees the usages.

The compiler only generate the code for the usages of the types it saw, not for every type. So the more types used with the template, the longer it compiles.