πŸ“”An Overview of Templates

Templates:

Templates in C++ allow you to define generic types and functions that can work with different data types without explicitly specifying the type. Templates are defined using the template keyword. Here's an example of a template function that swaps two values:

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

int main() {
    int x = 5, y = 10;
    swapValues(x, y);  // Swaps the values of x and y
    cout << "x: " << x << ", y: " << y << endl;
    
    double a = 3.14, b = 2.71;
    swapValues(a, b);  // Swaps the values of a and b
    cout << "a: " << a << ", b: " << b << endl;
    
    return 0;
}

Overloading Functions:

Function overloading allows you to define multiple functions with the same name but different parameters. The compiler determines the appropriate function to call based on the arguments provided. Here's an example of function overloading:

void printNumber(int num) {
    cout << "Integer number: " << num << endl;
}

void printNumber(double num) {
    cout << "Floating-point number: " << num << endl;
}

int main() {
    int x = 5;
    double y = 3.14;
    
    printNumber(x);  // Calls the printNumber function with int parameter
    printNumber(y);  // Calls the printNumber function with double parameter
    
    return 0;
}

Template Functions:

Template functions are functions that can work with multiple types using a template parameter. Here's an example of a template function that finds the maximum of two values:

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

int main() {
    int x = 5, y = 10;
    cout << "Max: " << findMax(x, y) << endl;  // Finds the maximum of two integers
    
    double a = 3.14, b = 2.71;
    cout << "Max: " << findMax(a, b) << endl;  // Finds the maximum of two doubles
    
    return 0;
}

Specializing a Template Function:

Template function specialization allows you to provide a specific implementation for a particular type. Here's an example of template function specialization for finding the maximum of two strings:

template <>
string findMax(string a, string b) {
    return (a.length() > b.length()) ? a : b;
}

int main() {
    string str1 = "Hello";
    string str2 = "World";
    
    cout << "Max: " << findMax(str1, str2) << endl;  // Finds the longer string
    
    return 0;
}

Disambiguation under Specialization:

When specializing template functions, it's important to disambiguate them from the generic template function. Here's an example showing how to disambiguate a specialized template function:

template <typename T>
void process(T value) {
    cout << "Generic Template" << endl;
}

template <>
void process<int>(int value) {
    cout << "Specialized Template for int" << endl;
}

int main() {
    int x = 5;
    process(x);  // Calls the specialized template for int
    
    double y = 3.14;
    process(y);  // Calls the generic template
    
    return 0;
}

Template Classes:

Templates can also be used to create generic classes. Here's an example of a template class called Stack that implements a stack data structure:

template <typename T>
class Stack {
private:
    vector<T> elements;

public:
    void push(T value) {
        elements.push_back(value);
    }

    T pop() {
        T value = elements.back();
        elements.pop_back();
        return value;
    }
};

int main() {
    Stack<int> intStack;
    intStack.push(5);
    intStack.push(10);
    intStack.push(15);
    cout << intStack.pop() << endl;  // Outputs 15

    Stack<string> stringStack;
    stringStack.push("Hello");
    stringStack.push("World");
    cout << stringStack.pop() << endl;  // Outputs "World"

    return 0;
}

An Array Template Class:

Here's an example of a template class called Array that represents a dynamic array:

template <typename T>
class Array {
private:
    T* elements;
    int size;

public:
    Array(int size) : size(size) {
        elements = new T[size];
    }

    ~Array() {
        delete[] elements;
    }

    T& operator[](int index) {
        return elements[index];
    }
};

int main() {
    Array<int> intArray(5);
    intArray[0] = 10;
    intArray[1] = 20;
    cout << intArray[0] << endl;  // Outputs 10

    Array<double> doubleArray(3);
    doubleArray[0] = 3.14;
    cout << doubleArray[0] << endl;  // Outputs 3.14

    return 0;
}

Instantiating a Template Class Object:

To create an object of a template class, you need to specify the template parameter. Here's an example of instantiating a template class object:

template <typename T>
class MyClass {
private:
    T value;

public:
    MyClass(T value) : value(value) {}

    void print() {
        cout << "Value: " << value << endl;
    }
};

int main() {
    MyClass<int> obj1(5);
    obj1.print();  // Outputs "Value: 5"

    MyClass<string> obj2("Hello");
    obj2.print();  // Outputs "Value: Hello"

    return 0;
}

Friends of Template Classes:

A template class can declare other classes or functions as friends using the friend keyword. Here's an example of a template class with a friend function:

template <typename T>
class MyTemplateClass {
private:
    T value;

public:
    MyTemplateClass(T value) : value(value) {}

    template <typename U>
    friend void printValue(const MyTemplateClass<U>& obj);
};

template <typename U>
void printValue(const MyTemplateClass<U>& obj) {
    cout << "Value: " << obj.value << endl;
}

int main() {
    MyTemplateClass<int> obj(5);
    printValue(obj);  // Outputs "Value: 5"

    return 0;
}

Templates with Multiple Type Parameters:

Templates can have multiple type parameters, allowing you to work with multiple types simultaneously. Here's an example of a template function with multiple type parameters:

template <typename T, typename U>
void printPair(T first, U second) {
    cout << "Pair: " << first << ", " << second << endl;
}

int main() {
    int x = 5;
    double y = 3.14;
    printPair(x, y);  // Outputs "Pair: 5, 3.14"

    string str = "Hello";
    char ch = '!';
    printPair(str, ch);  // Outputs "Pair: Hello, !"

    return 0;
}

Non-Class-type Parameters for Template Classes:

Template classes can also have non-class-type parameters, such as integers or enums. Here's an example of a template class with a non-class-type parameter:

template <int Size>
class FixedArray {
private:
    int array[Size];

public:
    void setValue(int index, int value) {
        array[index] = value;
    }

    int getValue(int index) {
        return array[index];
    }
};

int main() {
    FixedArray<5> arr;
    arr.setValue(0, 10);
    cout << arr.getValue(0) << endl;  // Outputs 10

    FixedArray<10> arr2;
    arr2.setValue(5, 20);
    cout << arr2.getValue(5) << endl;  // Outputs 20

    return 0;
}

Comments Regarding Templates:

When working with templates, it's important to keep in mind a few considerations:

  • Templates are resolved at compile-time, so template code must be visible during compilation.

  • Template functions can be defined in header files to make them accessible across multiple source files.

  • Templates can have default template arguments, allowing you to provide a default type if one is not explicitly specified.

  • Templates can be specialized for specific types to provide custom behavior for those types.

Templates and Inheritance:

Templates and inheritance can be used together in C++ to create generic base classes and derived classes. Templates allow you to define a class or function that can work with multiple types, while inheritance allows you to create a hierarchy of classes with shared functionality.

Here's an example that demonstrates how templates and inheritance can be combined:

template <typename T>
class BaseClass {
protected:
    T value;

public:
    BaseClass(T value) : value(value) {}

    void printValue() {
        cout << "Value: " << value << endl;
    }
};

template <typename T>
class DerivedClass : public BaseClass<T> {
public:
    DerivedClass(T value) : BaseClass<T>(value) {}

    void printDoubleValue() {
        cout << "Double Value: " << this->value * 2 << endl;
    }
};

int main() {
    DerivedClass<int> obj(5);
    obj.printValue();         // Outputs "Value: 5"
    obj.printDoubleValue();   // Outputs "Double Value: 10"

    DerivedClass<double> obj2(3.14);
    obj2.printValue();        // Outputs "Value: 3.14"
    obj2.printDoubleValue();  // Outputs "Double Value: 6.28"

    return 0;
}

In this example, we have a template base class BaseClass, which has a member variable value of type T and a member function printValue() that prints the value. The derived class DerivedClass is also a template class that inherits from BaseClass<T>. It adds a new member function printDoubleValue(), which doubles the value and prints it.

By using templates, we can create instances of BaseClass and DerivedClass with different types. In the main() function, we create objects of DerivedClass with int and double types and call their member functions to demonstrate the inheritance and template functionality.

Templates and inheritance together provide a powerful mechanism for creating generic and reusable code that can work with different types.

Last updated