Templates in C++

In this blog post, you will learn about templates in C++ with the help of programming examples.

Templates are powerful features of C++ that allow us to write generic programs. It allows us to define a family of classes, functions, or variables, an alias for a family of types, or a concept. That means as a programmer, we do not need to write the same class or function for different data types.

Don’t worry you will be able to understand what I am saying at the end of this article. So let’s start this article with the introduction of C++ templates.

 

Introduction of C++ templates:

In C++, a template is used to generate a generic code but it is not only used to generate a generic code but much more than the generic code.

Let’s consider a program for a better understanding of how templates generate a generic code.

Suppose you have a requirement to find the max value of two given integer numbers.

What you will do?

Defenatlry, you will create a simple program to find the max number among two given numbers. Like the below-mentioned C++ code.

int max(const int& a, const int& b)
{
    return ((a > b) ? a : b);
}

 

But the actual problem occurs when your requirement is changed and now you have to find the maximum of two given numbers of any given types int, float, double, or long int.

So in that situation, you need to repeat the same code for different data types. Because C++ is a strongly-typed language, which means requires all variables to have a specific type, either explicitly declared by the programmer or deduced by the compiler.

Copies of the same function for multiple data types:

//function for integer
int max(const int& a, const int& b)
{
    return ((a > b) ? a : b);
}


//function for float
float max(const float& a, const float& b)
{
    return ((a > b) ? a : b);
}


//function for double
int max(const double& a, const double& b)
{
    return ((a > b) ? a : b);
}


//function for long int
int max(const long int& a, const long int& b)
{
    return ((a > b) ? a : b);
}

 

Like me, you would also thinking, that it is ridiculous to repeat the same code multiple times, and how to write a generic function instead of repeating the same function.

So now the question is that what is the solution to this problem?

Don’t worry C++ provides the solution to this problem. You can solve this issue using the C++ templates. It enables you to define the operations of a class or function and gives the flexibility to use it on any concrete type.

For example, you can define a max function template like this:

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

 

The above-mentioned function describes a template for a generic function with a single type parameter “T”. The parameters (a and b) and the return value of this function has type “T”.  But if you want; you can use any name besides the “T”, but “T” is very common, so I have taken “T”.

Now I believe one question is coming to your mind what is basically T and typename in the template?

It is obvious to come to this question in your mind.

Basically in code, “T” is a template parameter and the typename is a keyword that says that this parameter is a placeholder for a type. When the function is invoked, the compiler replaces every instance of T with the concrete type parameter that the user specifies or that the compiler deduces.

To use the above-mentioned function template we use the following format for the function call.

Function_Name <type> (parameters);

The process in which the compiler generates a function from a template is referred to as template instantiation; max<int> is an instantiation of the template max<T>.

 

Types of templates:

In C++, we can implement the template in the following ways:

  1. Class Template: A family of classes that may be nested classes.
  2. Function template: A family of functions that may be member functions.
  3. Alias template: An alias to a family of types (since C++11).
  4. Variable template: A family of variables (since C++14).
  5. Constraints and concepts: A concept (since C++20).

 

So let’s learn all the above template entities one by one with the help of programming examples.

 

1. Class Template:

A class template defines the layout and operations for an unbounded set of related types. It is useful to create a class template when a class defines something that is independent of the data type. It makes our code shorter and more manageable.

Syntax of the class template:

template <class T> class class_name
{
   // class body
};

In the syntax above:

T is a template parameter.

class is a keyword that says T is a placeholder for a type. If you want you can use the typename keyword beside the class keyword in the basic case of specifying a template.

 

There are some pre-defined examples of class templates in C++. They are Array, LinkedList, Stack, Queue, etc. Let’s take an example to understand the class template syntax and its working.

#include <iostream>
#include <assert.h>
// Declaring a template class named Array.
template <typename T, unsigned int SZ>
class Array
{
public:
    // Constructor of Array class.
    explicit Array() : m_data() {}

    //member function to returns a reference of the specified index.
    T& operator[](unsigned int index)
    {

        assert(index < SZ);
        return m_data[index];
    }

    //return the size of array
    unsigned int size() const
    {
        return SZ;
    }

private:
    // An array of type T.
    T m_data[SZ];
};

int main()
{
    /****************Create an integer array*****************/
    std::cout<<"Create integer array------\n";
    Array<int,10> intArray;

    std::cout << "Array Size = " << intArray.size() <<std::endl;

    //Store value at 0th Index
    intArray[0] = 100;

    std::cout << "Value at 0th Index = " << intArray[0]<<std::endl;


    /****************Create an float array*****************/
    std::cout<<"\n\nCreate float array-------\n";
    Array<float,5> floatArray;

    std::cout << "Array Size = " << floatArray.size() <<std::endl;

    //Store value at 0th Index
    floatArray[0] = 10.555;

    std::cout << "Value at 0th Index = " << floatArray[0]<<std::endl;


    /****************Create an char array*****************/
    std::cout<<"\n\nCreate char array-------\n";
    Array<char,3> charArray;

    std::cout << "Array Size = " << charArray.size() <<std::endl;

    //Store value at 0th Index
    charArray[0] = 'A';

    std::cout << "Value at 0th Index = " << charArray[0]<<std::endl;

    return 0;
}

Output:

Create integer array------
Array Size = 10
Value at 0th Index = 100


Create float array-------
Array Size = 5
Value at 0th Index = 10.555


Create char array-------
Array Size = 3
Value at 0th Index = A

Process returned 0 (0x0)   execution time : 0.115 s
Press any key to continue.

 

In the above example, you can see it is possible for a single class template Array to provide an unbounded set of class definitions: one class Array<T> for every type T, each describing an array of elements of type T.

 

2. Function Template:

Like the class templates, we can also create function templates. A function template defines an unbounded set of related functions.

It is useful to create a function template when a function defines something that is independent of the data type. A function to swap two items is a good example.

Basic Syntax of the function template:

template <typename T>
T functionName(T parameter1, T parameter2, ...)
{
    // code
}

In the syntax above:

1. T is a template parameter.

2. typename is a keyword that says T is a placeholder for a type. If you want you can use the class keyword beside the typename keyword in the basic case of specifying a template.

 

Now let’s see how we can create a function template for swapping two objects. Consider the below code example.

// C++ Program to learn
// Use of template
#include <iostream>


// One function works for all data types. This would work
// even for user defined types if operator '=' is overloaded

//function template to swap to object
template <typename T> void swapTwoObj(T &obj1, T &obj2)
{
    T tmpObj(obj1);
    obj1 = obj2;
    obj2 = tmpObj;
}

int main()
{

    /****************Swap two integer*****************/
    std::cout<<"------------Swap Two Integer------\n\n";
    int value1 = 27;
    int value2 = 6;

    std::cout<<"Before the swap...\n";
    std::cout<< "value1 = "<< value1<<std::endl;
    std::cout<< "value2 = "<< value2<<std::endl;


    swapTwoObj(value1, value2 );

    std::cout<<"\n\nAfter the swap...\n";
    std::cout<< "value1 = "<< value1<<std::endl;
    std::cout<< "value2 = "<< value2<<std::endl;


    /****************Swap two Float*****************/
    std::cout<<"\n\n-------------Swap Two Float------\n\n";
    float value3 = 27.6;
    float value4 = 6.27;

    std::cout<<"Before the swap...\n";
    std::cout<< "value3 = "<< value3<<std::endl;
    std::cout<< "value4 = "<< value4<<std::endl;


    swapTwoObj(value3, value4 );

    std::cout<<"\n\nAfter the swap...\n";
    std::cout<< "value3 = "<< value3<<std::endl;
    std::cout<< "value4 = "<< value4<<std::endl;
    return 0;
}

 

The above code defines a family of functions that swap two given objects. That means it can swap char, int, float..,.etc, or other user-defined types. Also, classes if the class’s copy constructor and assignment operator are properly defined.

Besides copying the function template also prevents you from swapping two different object types.

Now you are thinking about how it can prevent.

So the reason is that compiler knows the types of the obj1 and obj2 parameters at compile time. Consider the below example code,

// C++ Program to learn
// Use of template
#include <iostream>


// One function works for all data types. This would work
// even for user defined types if operator '=' is overloaded
//function template to swap to object
template <typename T> void swapTwoObj(T &obj1, T &obj2)
{
    T tmpObj(obj1);
    obj1 = obj2;
    obj2 = tmpObj;
}


int main()
{
    int value1 = 27;
    long int value2 = 6;
    std::cout<<"Before the swap...\n";

    std::cout<< "value1 = "<< value1<<std::endl;
    std::cout<< "value2 = "<< value2<<std::endl;

    //try two swap int and long int
    swapTwoObj(value1, value2 ); //Get compile time error

    return 0;
}

Output: Compile time error.

 

Alias template (Since C++11):

An alias template is a name that refers to a family of types. OR you can say that name of the alias template is a template name.

Syntax:

Let’s see how to use an alias template; the following is the syntax:

template < template-parameter-list >

using identifier attr (optional) = type-id ;

 

Example:

The following code snippet presents the idea for the class template Area.

template <typename T, typename SZ>
class Array
{
   ....
};

 

The array has two template parameters. One is the type parameter T and another is the non-type parameter SZ. I want to have two special arrays type; one for integer and the second for float. Using the template alias I can make both types of arrays. It increases the code readability.

//alias templates

template <unsigned int SZ>
using IntArray = Array<int, SZ>; // (1)


template <unsigned int SZ>
using FloatArray = Array<float,SZ>; // (2)

 

In the above code snippet you can see that I have created two alias templates with the help of using keywords. The primary template Array has three parameters, with an alias template we reduce it to the one. Also in terms of readability newly created templates are more informative in comparison to the main template.

See the below example code for a better understanding of what I have discussed above.

#include <iostream>
#include <assert.h>


// Declaring a template class named Array.
template <typename T, unsigned int SZ>
class Array
{
public:
    // Constructor of Array class.
    explicit Array() : m_data() {}
    //member function to returns a reference of the specified index.
    T& operator[](unsigned int index)
    {
        assert(index < SZ);
        return m_data[index];
    }
    //return the size of array
    unsigned int size() const
    {
        return SZ;
    }
private:
    // An array of type T.
    T m_data[SZ];
};


//alias templates
template <unsigned int SZ>
using IntArray = Array<int, SZ>; // (1)

template <unsigned int SZ>
using FloatArray = Array<float,SZ>; // (2)



int main()
{
    //integer array
    IntArray<5> arr1;

    //Store value at 2nd Index
    arr1[2] = 100;
    std::cout << "Int Array:: Value at 2nd Index = " << arr1[2];


    //array of floats
    FloatArray<8> arr2;

    //Store value at 2nd Index
    arr2[2] = 27.6;
    std::cout << "\n\nFloat Array:: Value at 2nd Index = " << arr2[2]<<std::endl;


    return 0;
}

Output:

Int Array:: Value at 2nd Index = 100

Float Array:: Value at 2nd Index = 27.6

 

Variable template (since C++14):

A template declaration of a variable is a variable template. Also, a variable instantiated from a variable template is called an instantiated variable.

See the below example, where pi is a variable template and pi<int>, pi<float>, ..etc are instantiated variables.

#include <iostream>

// variable template
template<class T>
constexpr T pi = T(3.1415926535897932385L);

int main()
{

    std::cout << pi<float> << std::endl ;
    std::cout << pi<double> << std::endl ;

    std::cout << pi<long double> << std::endl ;

    return 0;
}

 

A static variable template at class scope is a static data member template. And static data member instantiated from a static data member template is called an instantiated static data member.

So in simple words, we can say that a variable template refers to a family of variables or static data members (template declaration can appear only as a namespace scope or class scope declaration).

#include <iostream>

struct SExmpl
{
   // declaration of a static data member template
   template <typename T>
   static T min;
};

// definition of a static data member template
template <typename T>
T SExmpl::min = {};

int main()
{

   SExmpl obj;

   // instantiated static data member
   obj.min<int> = 2;
   std::cout << obj.min<int> << std::endl;

   // instantiated static data member
   obj.min<float> = 1.6;
   std::cout << obj.min<float> << std::endl;

   // instantiated static data member
   obj.min<double> = 1.64555;
   std::cout << obj.min<double>;

   return 0;
}

Output:

2
1.6
1.64555

 

Recommended Articles for you: