Why Templates?
C++ requires us to use specific types to declare variables, functions, and other entities. However, a lot of code looks the same for different types. Especially if we implement algorithms, such as quicksort, or the behaviour of data structures, such as a linked list or a binary tree for different types, the code looks the same despite the type used. Suppose our programming language doesn't support a special language feature for this. In that case, we only have bad alternatives:
- We can implement the same behaviour repeatedly for each type that needs this behaviour.
- We can write generic code for a common base type such as Object or void*.
- We can use special preprocessors.
If we come from C, Java, or similar languages, we have done some or all of this before. However, each of these approaches has its drawbacks:
- If we implement a behaviour again and again, we reinvent the wheel. We make the same mistakes and avoid complicated but better algorithms because they lead to even more errors.
- If we write general code for a common base class, we lose the benefit of type-checking. In addition, classes may be required to be derived from special base classes, which makes it more difficult to maintain your code.
- We lose the advantage of formatted source code using a special pre-processor such as the C/C++ pre-processor. Code is replaced by some "stupid text replacement mechanism" without any idea of scope and types.
Templates offer a solution to this issue, free from these limitations. They are essentially functions or classes designed for unspecified types. By utilizing templates, we provide the types as arguments, whether explicitly or implicitly. Since templates are integral language components, we benefit from comprehensive type-checking and scope assistance. Presently, templates are extensively employed in software development. For instance, within the C++ standard library, a vast majority of the code consists of template implementations. This library furnishes sorting algorithms for arranging objects and values of specified types, container classes for managing elements of chosen types, customizable character-styled strings, and more. Nevertheless, this merely scratches the surface. Templates also enable us to parameterize behavior, enhance code efficiency, and customize information. These aspects will be explored in subsequent sections. Let's initiate our exploration with some basic template examples.
Specializations of Class Templates
We have the ability to focus on a specific set of template arguments within a class template. Similar to how we can overload function templates, specializing in class templates enables us to enhance implementations for particular types or address issues with specific types when instantiating the class template. Nevertheless, when we opt to specialize a class template, it is necessary to specialize all member functions as well. While it is feasible to specialize an individual member function, once this is done, we lose the ability to specialize the entire class.
To create a specialized class template, it is necessary to define the class using a leading template<> and specify the types for which the class template is specialized. These types serve as template arguments and should be explicitly stated immediately after the class name:
template<>
class Stack {
…
};
For these particular specializations, every declaration of a member function needs to be specified as a standard member function, where each instance of T is substituted with the specialized type:
Partial Specialization
Class templates can undergo partial specialization, allowing the definition of specific implementations for unique scenarios, although certain template parameters still require user definition. An illustration of this is provided for the subsequent class template:
template <typename T1, typename T2>
class MyClass {
…
};
The following partial specializations are possible:
// partial specialization: both template parameters have the same type of template
class MyClass<T, T> {
…
};
// partial specialization: the second type is int
template <typename>
class MyClass<T, int>
{
…
};
// partial specialization: both template parameters are pointer types
template <typename T1, typename T2>
class MyClass<T1*,T2*>
{
…
};
The provided example demonstrates the correspondence between each declaration and its respective template in use:
MyClass<int, float> mif; // uses MyClass<T1, T2>
MyClass<float,float> mff; // uses MyClass<T,T>
MyClass <float,int>mfi; // uses MyClass<T,int>
MyClass<int*,float*>mp; // uses MyClass<T1*,T2*>
If multiple partial specializations match equally well, the declaration becomes ambiguous:
MyClass<int, int> m; // ERROR: matches MyClass
// and MyClass<T,int>
MyClass<int*, int*> m; // ERROR: matches MyClass
// and MyClass<T1*,T2*>
Function Templates
Function templates are unique functions designed to work with generic types. They enable the creation of a template function that can adjust its functionality to various types or classes without the need to duplicate the entire code for each type.
In C++, this can be accomplished through template parameters. A template parameter serves as a unique parameter enabling the passing of a type as an argument. Similar to how standard function parameters transmit values to a function, template parameters facilitate the passing of types to a function. Subsequently, these function templates can employ these parameters in the same manner as any other standard type.
The structure for defining function templates with type parameters is:
template <class identifier> function_declaration;
template <typename identifier> function_declaration;
The sole contrast between the two prototypes lies in the utilization of either the keyword class or the keyword typename. Their usage is interchangeable as both terms carry identical meanings and exhibit similar behavior.
For instance, to generate a template function that outputs the larger of two objects, we might employ:
template <class myType>
myType GetMax (myType a, myType b)
{
return (a>b?a:b)
}
Here we've established a template function with myType as its template parameter. This particular parameter denotes a type that remains unspecified at this stage, yet it can be effectively applied within the template function just like any other type. The GetMax function template is designed to yield the larger value among two parameters of this as of now unspecified type. In order to utilize this function template, we adhere to the subsequent format for invoking the function:
function_name <type> (parameters);
For instance, to invoke the GetMax function to compare two integer values of int type, we can code:
int x,y;
GetMax <int> (x,y);
When the compiler comes across a call to a template function, it employs the template to automatically produce a function that substitutes every instance of myType with the type provided as the specific template parameter (such as int in this instance) and then executes it. This mechanism is carried out automatically by the compiler without the programmer's direct involvement.
Here is the entire example:
// function template
#include <iostream>
using namespace std;
template <class T>
T GetMax (T a, T b) {
T result;
result = (a>b)? a : b;
return (result);
}
int main () {
int i=5, j=6, k;
long l=10, m=5, n;
k=GetMax<int>(i,j);
n=GetMax<long>(l,m);
cout << k << endl;
cout << n << endl;
return 0;
}
In this scenario, we opted for T as the template parameter designation instead of myType for brevity and its widespread use. Nonetheless, any identifier of your preference can be utilized.
We employed the function template GetMax twice in the previous example. Initially, it was utilized with integer arguments, and subsequently with long arguments. On both occasions, the compiler instantiated and executed the correct version of the function accordingly.
Within the GetMax template function, type T is employed not only for declaring new objects of that same type.
T result:
Consequently, the output will be an object of identical type to the parameters a and b once the function template is specialized with a particular data type.
In this particular scenario, when the generic type T is employed as an argument for GetMax, the compiler is capable of determining the specific data type to instantiate without the need for explicit declaration within angle brackets (as demonstrated earlier with <int> and <long>). Therefore, an alternative way to express this could be:
int i,j;
GetMax (i,j);
Given that both i and j are integers, the compiler can automatically infer that the template parameter must also be an integer. This implicit approach yields identical outcomes:
// function template II
#include <iostream>
using namespace std;
template <class T>
T GetMax (T a, T b) {
return (a>b?a:b);
}
int main () {
int i=5, j=6, k;
long l=10, m=5, n;
k=GetMax(i,j);
n=GetMax(l,m);
cout << k << endl;
cout << n << endl;
return 0;
}
In this scenario, we invoked our function template GetMax without explicitly defining the type within angle brackets <>. The compiler autonomously identifies the required type for each invocation.
Since our template function is designed with a single template parameter (class T) and the function template requires two parameters of type T, it is not possible to invoke the function template using two objects of distinct types as arguments:
int i;
long l;
k=GetMax(i,l);
It is not appropriate as the GetMax function template requires two arguments that are of identical types, whereas in this invocation, we are passing objects of differing types.
We can also create function templates that can handle multiple parameter types by declaring additional template parameters within the angle brackets. For instance:
template <class T, class U>
T GetMin (T a, U b) {
return (a<b?a:b);
}
In this scenario, our function template GetMin takes in two parameters of distinct types and yields an object of matching type as the initial parameter (T) given. Following this definition, we could invoke GetMin with the subsequent input:
int i,j;
long l;
i = GetMin<int,long> (j,l);
or simply:
i = GetMin (j,l);
Even with distinct data types for variables "j" and "l", the compiler is capable of deducing the correct instantiation.
Class Templates
We can also define class templates to enable a class to contain members utilizing template parameters as data types. For instance:
template <class T>
class mypair {
T values [2];
public:
mypair (T first, T second)
{
values[0]=first; values[1]=second;
}
};
The recently created class is designed to hold a pair of elements of any permissible data type. For instance, if we wish to instantiate an object of this class to retain two integer values of type int, such as 115 and 36, the syntax would be:
mypair myobject (115, 36);
this identical class could also serve to instantiate an object for storing any different data type:
mypair myfloats (3.0, 2.18);
The single member function in the preceding class template has been implemented inline within the class declaration. When defining a function member outside the class template declaration, it is necessary to always begin that definition with the template keyword prefix: <...>.
// class templates
#include <iostream>
using namespace std;
template <class T>
class mypair {
T a, b;
public:
mypair (T first, T second)
{a=first; b=second;}
T getmax ();
};
template <class T>
T mypair<T>::getmax ()
{
T retval;
retval = a>b? a : b;
return retval;
}
int main () {
mypair <int> myobject (100, 75);
cout << myobject.getmax();
return 0;
}
Output:
Observe the syntax used to define the member function getmax:
template T
mypair::getmax ()
This statement involves three T's: The initial one represents the template parameter. The second T denotes the function's return type. The third T enclosed in angle brackets serves as a specification, indicating that the template parameter for this function is also the class template parameter.
Template Specialization
If there is a need to specify a unique behavior for a template based on a specific type passed as a parameter, it is possible to create a specialization of that template. For instance, consider a basic class named mycontainer designed to hold a single element of any type, featuring a single member function named increase to increment its value. However, upon realizing that a char type element would benefit from a distinct implementation with an uppercase member function, a decision is made to define a class template specialization tailored for this scenario:
// template specialization
#include <iostream>
using namespace std;
// class template:
template <class T>
class mycontainer {
T element;
public:
mycontainer (T arg) {element=arg;}
T increase () {return ++element;}
};
// class template specialization:
template <>
class mycontainer <char> {
char element;
public:
mycontainer (char arg) {element=arg;}
char uppercase ()
{
if ((element>='a')&&(element<='z'))
element+='A'-'a';
return element;
}
};
int main () {
mycontainer<int> myint (7);
mycontainer<char> mychar ('j');
cout << myint.increase() << endl;
cout << mychar.uppercase() << endl;
return 0;
}
Output:
In the following code snippet, you can observe the syntax employed for class template specialization:
template <> class mycontainer <char> { ... };
At the outset, it's important to observe that we prefix the class template name with an empty template <> parameter list. This action is taken to explicitly define it as a template specialization.
However, the specialization parameter following the class template name holds greater significance than this prefix. This parameter is crucial as it specifies the type for which we are defining a template class specialization, such as 'char'. It is essential to observe and understand the distinctions between the generic class template and its specialization:
template <class T> class mycontainer { ... };
template <> class mycontainer <char> { ... };
When specifying specializations for a template class, it is necessary to provide definitions for all its members, including those that are identical to the generic template class. This is because there is no automatic transfer of members from the generic template to the specialization.