Skillversitas

Templates in C++

Templates are one of the most powerful features in C++, allowing us to write generic code that can work with various data types. With templates, we can write a single function or a single class that can be used with int, float, string, or any other data type without having to rewrite the same code over and over.

This concept is known as generic programming. Instead of writing a max function for int, then a separate max function for float, and so on, just write one template function that can handle all of those data types. The compiler will automatically create a specific version of the function for each data type used.

C++ supports several kinds of templates: function templates, class templates, variable templates (C++14), and template specialization. Starting with C++20, there is also the concepts feature, which allows us to constrain the data types used in a template.

Code Example

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

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

// Variable template (C++14)
template <typename T>
const T pi = T(3.14159265358979323846);

// Class template
template <typename T>
class Stack {
private:
    static const int MAX_SIZE = 100;
    T data[MAX_SIZE];
    int topIndex;

public:
    Stack() : topIndex(-1) {}

    void push(T value) {
        if (topIndex >= MAX_SIZE - 1) {
            cout << "Stack is full!" << endl;
            return;
        }
        data[++topIndex] = value;
    }

    T pop() {
        if (isEmpty()) {
            cout << "Stack is empty!" << endl;
            return T();
        }
        return data[topIndex--];
    }

    T top() const {
        if (isEmpty()) {
            cout << "Stack is empty!" << endl;
            return T();
        }
        return data[topIndex];
    }

    bool isEmpty() const {
        return topIndex == -1;
    }

    int size() const {
        return topIndex + 1;
    }
};

// Template specialization for the string type
template <>
class Stack<string> {
private:
    static const int MAX_SIZE = 50;
    string data[MAX_SIZE];
    int topIndex;

public:
    Stack() : topIndex(-1) {}

    void push(string value) {
        if (topIndex >= MAX_SIZE - 1) {
            cout << "String stack is full!" << endl;
            return;
        }
        data[++topIndex] = value;
    }

    string pop() {
        if (isEmpty()) {
            return "";
        }
        return data[topIndex--];
    }

    string top() const {
        if (isEmpty()) {
            return "";
        }
        return data[topIndex];
    }

    bool isEmpty() const {
        return topIndex == -1;
    }

    int size() const {
        return topIndex + 1;
    }
};

// Function template with multiple type parameters
template <typename T, typename U>
void displayPair(T first, U second) {
    cout << "Pair: (" << first << ", " << second << ")" << endl;
}

int main() {
    // Function template
    cout << "=== Function Template ===" << endl;
    cout << "max(10, 20) = " << maximum(10, 20) << endl;
    cout << "max(3.14, 2.71) = " << maximum(3.14, 2.71) << endl;
    cout << "max('a', 'z') = " << maximum('a', 'z') << endl;

    // Variable template
    cout << "\n=== Variable Template ===" << endl;
    cout << "pi (double) = " << pi<double> << endl;
    cout << "pi (float) = " << pi<float> << endl;

    // Class template with int
    cout << "\n=== Class Template (int) ===" << endl;
    Stack<int> intStack;
    intStack.push(10);
    intStack.push(20);
    intStack.push(30);
    cout << "Top: " << intStack.top() << endl;
    cout << "Pop: " << intStack.pop() << endl;
    cout << "Size: " << intStack.size() << endl;

    // Class template with double
    cout << "\n=== Class Template (double) ===" << endl;
    Stack<double> doubleStack;
    doubleStack.push(1.1);
    doubleStack.push(2.2);
    cout << "Top: " << doubleStack.top() << endl;

    // Class template specialization for string
    cout << "\n=== Class Template Specialization (string) ===" << endl;
    Stack<string> stringStack;
    stringStack.push("Hello");
    stringStack.push("World");
    cout << "Top: " << stringStack.top() << endl;
    cout << "Pop: " << stringStack.pop() << endl;

    // Multiple type parameters
    cout << "\n=== Multiple Type Parameters ===" << endl;
    displayPair(10, 3.14);
    displayPair(string("Name"), string("John"));

    return 0;
}

Code Explanation

  1. template <typename T> — A template declaration. typename can be replaced with class (both are the same). T is the data type parameter that will be substituted when the template is used.

  2. maximum(T a, T b) — A function template that takes two parameters of the generic data type T. The data type T is determined automatically based on the arguments passed in.

  3. template <typename T> const T pi = T(3.14159...) — A variable template (C++14). Allows declaring generic variables that can be used with various data types.

  4. Stack<T> — A class template that creates a generic stack. All class members use the data type T, so the stack can store any data type.

  5. template <> class Stack<string> — A template specialization. This is a special version of Stack that applies only to the string type. Useful when a particular type requires different treatment.

  6. Stack<int> intStack — When using a class template, we must declare the data type to be used. This is called a template argument.

  7. template <typename T, typename U> — A template can have more than one data type parameter. The displayPair function uses two different types at the same time.

  8. Template argument deduction — The compiler automatically determines the data type T based on the arguments. For example, maximum(10, 20) automatically creates the int version of the function.

Everyday Use Cases

Imagine you are building an online store application. You have many kinds of data that need to be sorted: product prices (double), stock quantities (int), product names (string). Without templates, you would have to write a separate sort function for each data type.

With templates, just write one generic sort function that can sort arrays of any data type. A template stack can be used for a shopping history system (storing recently viewed items), a template queue for an order queue, and so on.

// Sorting various data types with a single function template
template <typename T>
void bubbleSort(T arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                T temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

// Can be used for int, double, string, and others
int price[] = {50000, 10000, 30000};
string product[] = {"Laptop", "Mouse", "Keyboard"};
bubbleSort(price, 3);    // Sorts the prices
bubbleSort(product, 3);   // Sorts the product names

Conclusion

Templates enable the writing of generic, flexible, and reusable code. Function templates create functions that can work with various data types, class templates create generic classes, and template specialization allows special treatment for specific types. With templates, code becomes more concise, easier to maintain, and avoids code duplication.