Contents

Templates

Introduction to Templates in C++

Templates are a powerful feature in C++ that allow for generic programming. The concept behind templates is to enable functions or classes to work with any data type without rewriting code for each type. For example, a software developer may need to create a sorting function that works for various data types like integers, floating-point numbers, or characters. Instead of writing separate functions for each data type, a template can be used to write one generic function.

C++ introduces two keywords for working with templates: template and typename. The keyword typename can also be replaced with class for convenience.

How Templates Work

Templates are processed at compile time, which makes them similar to macros. However, unlike macros, templates undergo type-checking during compilation. This ensures that the code is syntactically correct for the specific data type. While the source code contains a single version of the function or class, the compiler generates multiple instances for different data types.

Function Templates

Function templates allow for the creation of generic functions that can operate on different data types. Some commonly used function templates include sort(), max(), min(), and printArray().

Here’s an example demonstrating a function template:

				
					// C++ Program to demonstrate the use of templates
#include <iostream>
using namespace std;

// A function template that works for any data type
template <typename T> T getMax(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    // Calling getMax for different data types
    cout << getMax<int>(5, 10) << endl;         // int
    cout << getMax<double>(4.5, 2.3) << endl;   // double
    cout << getMax<char>('a', 'z') << endl;     // char

    return 0;
}

				
			

Output:

				
					1.1 2.2 3.3 4.4 5.5

				
			
Multiple Template Parameters

It is possible to pass more than one type of argument to templates. This is particularly useful when you need a class or function to work with multiple types of data.

				
					// C++ Program to demonstrate multiple template parameters
#include <iostream>
using namespace std;

template <class T1, class T2> class Pair {
    T1 first;
    T2 second;

public:
    Pair(T1 x, T2 y) : first(x), second(y) {
        cout << "Pair initialized" << endl;
    }

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

int main() {
    Pair<int, double> p1(10, 3.14);
    p1.display();

    Pair<string, char> p2("Hello", 'A');
    p2.display();

    return 0;
}

				
			

Output:

				
					Pair initialized
First: 10, Second: 3.14
Pair initialized
First: Hello, Second: A

				
			
Default Template Parameters

Just like regular function parameters, templates can also have default arguments. This allows for flexibility in specifying the template arguments.

				
					// C++ Program to demonstrate default template parameters
#include <iostream>
using namespace std;

template <class T1, class T2 = int> class MyClass {
public:
    T1 data1;
    T2 data2;

    MyClass(T1 x, T2 y = 0) : data1(x), data2(y) {
        cout << "Constructor called" << endl;
    }

    void show() {
        cout << "Data1: " << data1 << ", Data2: " << data2 << endl;
    }
};

int main() {
    MyClass<double> obj1(4.5);
    obj1.show();

    MyClass<int, char> obj2(100, 'A');
    obj2.show();

    return 0;
}

				
			

Output:

				
					Constructor called
Data1: 4.5, Data2: 0
Constructor called
Data1: 100, Data2: A

				
			

Templates in C++: An Overview

Templates in C++ are a powerful feature that enables us to write generic code that can work with any data type, including user-defined types. This avoids the need to write and maintain multiple versions of functions or classes for different data types. For instance, a generic sort() function can be created to sort arrays of any type. Similarly, a class like Stack can be implemented to handle various types.

Template Specialization: Customizing Code for Specific Data Types

Sometimes, we may need different behavior for specific data types within a template. For example, in a large project, we might use Quick Sort for most data types, but for char, we could opt for a more efficient counting sort due to the limited range of possible values (256). C++ allows us to define a special version of a template for a particular data type, a feature known as template specialization.

Function Overloading: The Traditional Approach

Function overloading allows us to define multiple functions with the same name but different parameter types. Here’s an example of overloaded functions:

				
					#include <iostream>
using namespace std;

void show(int, int);
void show(double, double);
void show(char, char);

int main() {
    show(2, 5);
    show(2.6, 7.6);
    return 0;
}

void show(int a, int b) {
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
}

void show(double a, double b) {
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
}

				
			

Output:

				
					a = 2
b = 5
a = 2.6
b = 7.6

				
			

While function overloading works, it results in repeated code for different data types that perform the same task. This redundancy can be avoided using function templates.

Function Templates: Eliminating Code Duplication

A function template allows us to write a generic function that works for any data type. This reduces code duplication and simplifies maintenance.

Example of a generic sort function:

				
					// Generic sort function using templates
template <class T>
void sort(T arr[], int size) {
    // Implement Quick Sort for all data types
}

// Template specialization for char data type
template <>
void sort<char>(char arr[], int size) {
    // Implement Counting Sort for char
}

				
			

Output:

				
					a = 3
b = 8
a = 2.3
b = 4.6

				
			

Function Template Example: Printing the Maximum of Two Values

				
					#include <iostream>
using namespace std;

template <class T>
void getMax(T a, T b) {
    T result = (a > b) ? a : b;
    cout << "Maximum: " << result << endl;
}

int main() {
    getMax(5, 10);
    getMax(7.1, 3.9);
    getMax('A', 'Z');
    return 0;
}

				
			

Output:

				
					Maximum: 10
Maximum: 7.1
Maximum: Z

				
			
Template Specialization: Customizing Function Templates

Template specialization allows us to define a specific behavior for a particular data type.

Example of template specialization for int type:

				
					#include <iostream>
using namespace std;

template <class T>
void fun(T a) {
    cout << "Generic template: " << a << endl;
}

// Specialized version for int
template <>
void fun(int a) {
    cout << "Specialized template for int: " << a << endl;
}

int main() {
    fun(5.6);   // Calls the generic template
    fun(10);    // Calls the specialized template for int
    return 0;
}

				
			

Output:

				
					Generic template: 5.6
Specialized template for int: 10
				
			
Class Templates: Creating Generic Classes

Like functions, we can also create class templates that are independent of data types. This is useful for generic data structures such as arrays, linked lists, and stacks.

Example of a class template:

				
					#include <iostream>
using namespace std;

template <class T>
class Test {
public:
    Test() {
        cout << "General template object\n";
    }
};

// Specialized version for int
template <>
class Test<int> {
public:
    Test() {
        cout << "Specialized template for int\n";
    }
};

int main() {
    Test<int> obj1;    // Calls specialized template
    Test<double> obj2; // Calls general template
    return 0;
}

				
			

Output:

				
					Specialized template for int
General template object

				
			

Using Keyword in C++ STL

The using keyword in C++ provides a convenient way to specify which namespace, class, or function to use within a scope. This becomes particularly useful when working with large libraries or codebases that involve multiple namespaces. It helps avoid repeated typing of namespace prefixes and simplifies the code.

However, caution is required when using the using keyword, as it can potentially introduce naming conflicts, especially in large projects. Therefore, it’s best to apply it selectively to maintain clear and organized code.

Uses of the using Keyword in C++ STL

The using keyword can be utilized in several ways:

1. Using for Namespaces
2. Using for Inheritance
3. Using for Aliasing
4. Using for Directives

1. Using for Namespaces : The using keyword allows you to access entities in a particular namespace without having to specify the full namespace name repeatedly.

Example:

				
					#include <iostream>
using namespace std;

int main() {
    cout << "Hello from C++!" << endl;
    return 0;
}

				
			

Output:

				
					Value of a: 25

				
			

2. Using for Inheritance : In C++, the using keyword can also be used to inherit constructors from a base class into a derived class. This can save time by avoiding the need to redefine constructors in the derived class.

Example:

				
					#include <iostream>
using namespace std;

class Parent {
public:
    int a;

    Parent(int x) : a(x) {}
};

class Child : public Parent {
public:
    using Parent::Parent;  // Inheriting Parent's constructor
};

int main() {
    Child obj(25);
    cout << "Value of a: " << obj.a << endl;
    return 0;
}

				
			

Output:

				
					Number: 1234567890
				
			

3. Using for Aliasing : The using keyword in C++ can be used to create type aliases, which allow developers to define alternative names for existing types. This improves code readability and makes complex type declarations easier to manage.

Example:

				
					#include <iostream>
#include <vector>

using IntVector = std::vector<int>; // Alias for std::vector<int>

int main() {
    IntVector numbers = {1, 2, 3, 4, 5}; // Use alias
    for (int num : numbers) {
        std::cout << num << " ";
    }
    return 0;
}

				
			

Output:

				
					1 2 3 4 5

				
			

4. Using for Directives : The using keyword can also be used to bring specific entities from a namespace into the current scope, making the code more concise and readable.

Example:

				
					#include <iostream>
using std::cout;
using std::endl;

int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

				
			

Output:

				
					Hello, World!

				
			
Forms of the using Keyword

1. using namespace std;: This form brings all the members of the std namespace into your code.
2. using std::cout;: This form brings only the cout object into your code, which reduces the chances of naming conflicts.
3. using std::endl;: Similarly, this form is used to access only the endl object.

You can also apply the using keyword to your own custom namespaces.

Limitations of the using Keyword

While using can simplify code, it can also introduce problems if used carelessly:

  • Name collisions: If multiple namespaces have entities with the same name, using the using keyword indiscriminately can lead to ambiguity.
  • Global namespace: The using keyword cannot make types in the global or parent namespaces visible.
  • Static classes: It also cannot make static classes visible.

Example:

				
					#include <iostream>
using namespace std;

int main() {
    cout << "Using namespace example" << endl;
    return 0;
}

				
			

Output:

				
					Using namespace example

				
			

Example 2: Using Keyword with Selective Imports

				
					#include <iostream>
using std::cout;
using std::endl;

int main() {
    cout << "Selective using example" << endl;
    return 0;
}

				
			

Output:

				
					Selective using example