Welcome to the world of C++ Templates, a powerful but lesser-used feature of the C++ language, which can significantly improve developing clean code in any large or small-scale project. A template is essentially a design of a function or a class using generic data type. It empowers the programmer to write generic code which can work with different data types. This chapter will introduce you to the concept of templates with a brief history to understand where it all started. This chapter will act as the foundation for future chapters and take you gradually to the deeper details about templates.

Brief history of templates

The concept of templates is not new in C++. The consideration to incorporate templates as a feature started in the early ’80s, and the first formal proposal for templates came in the mid-’80s. The feature was implemented in the year 1991. Since then, the templates have evolved continuously and become a significant part of the C++ programming paradigm. In the following year (1992), some additional features were added: template instantiation and member template.

Alongside, works were ongoing to develop a standard library since the late ’70s. In 1998, Standard Template Library (STL) was first incorporated into the language. Almost everything in STL is written in templates. Later, C++11 brought in some significant new features such as variadic templates and alias templates. C++14 introduced new features such as variable templates and generic lambdas (related to templates). Then C++17 further introduced features such as “Compile-time if” and Class template argument deduction. With this basic information of templates’ evolution, let us understand the importance of templates in C++ programming in the next section.

Why do we need templates?

Templates are the way to generic programming. What does that mean? For example, let‘s say you want to find a minimum of two numbers. To achieve this, what are the different things coming into your mind? You might possibly be thinking about the following list of things at this point:

  • That you will accomplish this by writing a function that takes two numbers as input.
  • The types of the data for the two input numbers
  • The internal logic to determine the minimum of the two numbers.

If the data types of the input numbers change, then you need to rewrite the same function with the new data types. If there are three different types of data you need to work with (e.g. int, double, char), you need to write three different versions of the same function which is not a very elegant solution.

Let’s say you have written a function that returns a minimum of two integers. Now you realize you have to do the same for two doubles, that is, write a function that would return a minimum of two doubles. The internal logic of these two versions of the functions is identical, but the input data type is different. The possible implementation of the same could be as follows:

Figure 1.1 – Implementation using non-template functions

As you can see, the two versions of the min() function we have implemented are the same but with different data types. It is evident that there is a redundancy in source code here. How about being able to write a single version of the function which works with both int and double? This is where generic programming comes in. Generic programming provides a mechanism to the programmer to direct the compiler somewhat like this: “Hey compiler, my function may need to work with different input data types, hence I specified a generic data type in the definition; which data type to use will depend on the the arguments passed to this function.” As with this mechanism, you can design functions or classes with generic data types, also known as generic programming.

Template example from STL

Now, coming back to our minimum of two numbers problem: first, we will see how we can write a program using a generic function available in STL. STL provides a function called std::min(), which takes two input numbers of any type and returns the minimum of the two. Let’s have a look at the following code snippet:

min_numbers.cpp

#include <iostream>
#include <algorithm>
int main()
{
    int min_of_two = std::min(100, 200);
    std::cout <<"Min of 100 and 200 is " << min_of_two << std::endl;
    return 0;
}

The output of this program will look as follows:

Min of 100 and 200 is 100.

As you can see, the STL library function std::min() takes a pair of integers and returns the minimum of those two. But what’s so special about that? To understand that, let’s expand our program as follows:

min_numbers_2.cpp

#include <iostream>
#include <algorithm>
int main()
{
    int min_of_int = std::min(100, 200);
    std::cout << "Min of 100 and 200 is " << min_of_int << std::endl;
    double min_of_double = std::min(4.5, 2.3);
    std::cout << "Min of 4.5 and 2.3 is " << min_of_double << std::endl;
    return 0;
}

To compile the previous code, type the following command in a Linux console and press Enter: Here’s how the syntax looks like:

g++ <program-name>.cpp -o <output executable name>

In this case the command will be as shown next:

g++ min_numbers.cpp -o min_numbers

Here the name of the CPP file we are compiling is min_numbers.cpp and the name of the output executable is min_numbers. The name of the output file can be anything you like. if you do not specify an output filename then by default Linux names the binary executable as a.out. So, if you compile it with the following command then you will have an a.out file in the same directory:

$ g++ min_numbers.cpp -o min_numbers
$ ./a.out

The extension of the output file really doesn’t matter. Name it as you like, either keep or skip the extension if you want; Linux doesn’t care. To run the program, type the following command in the Linux console and press the Enter button:

./min_numbers

Output:

Min of 100 and 200 is 100
Min of 4.5 and 2.3 is 2.3

Exaplanation

You will notice that we have just added a new line in the second program and passed two double to the std::min() function. From the output, it is evident that the STL function std::min() can work both with integers and doubles. std::min() is a template function in the STL library. The header file we have to include is algorithm. As the library function std::min() is data type agnostic and can operate on any kind of data, it is a generic function. The internal C++ mechanism which makes it possible to write generic functions is the template.

Looking at a sample template

Just so that you have a flavor of the templates, let’s convert our min() function into a template function and see how it looks. A possible implementation of the min() function using the template is as follows:

template<typename T>
T min(T x, T y)
{
    return x > y ? y : x;
}

Please ignore the template function’s implementation details at this point, as we will discuss all of these in the coming chapters. The above single implementation of the min() function would work for int, double, and char type data. So at the very least, you have saved repeating the same source code two more times. The point here is to give you an idea of how templates can help reduce code redundancy.

Why is that important?

So, by now, you may have got a bit of an idea how by using the C++ template library (STL) can help reduce redundancy in the source code. In any project, the maintainability of source code is a significant issue. The challenge is not only to write the code but also to maintain it for years to come. The readability of the code significantly contributes to source code maintenance.

Templates can immensely improve readability by reducing redundant code. Source code goes through a long life cycle. Some may have written the source code in the first place, whereas someone else may later maintain the source code. The programmer(s) who writes the source code are not always the ones who debug it. Readability refers to how easy it is for someone other than the person who wrote the code to understand the code by reading it. Templates offer generic and hence compact code, consequently better readability.

Less redundant code means smaller code size. More lines of code also mean the possibility of more bugs. Templates help in writing bug-free code, bugs that creep in due to humanly mistakes. The compiler can automatically generate different versions of classes and functions using different data types using the template’s definition. Hence, the chances of accidental errors are less, and thus using templates would mean more reliable, clean, and maintainable source code. As we progress through the chapters, we will see templates offer much more than just code readability and maintainability. But these are great incentives to start with.

Source code repetition is not advisable due to the following potential issues:

  • Writing repeated code is time-consuming.
  • Writing the same code in multiple places is tedious and error-prone. The programmer may introduce a bug inadvertently while rewriting the same code. I blame that on the human mistake.
  • With repeated code, software maintenance becomes an issue. Every time you fix a bug in one place, you have to replicate that elsewhere as well. This can be very costly in a large project. Let’s say you have written three versions of a function, namely foo(int, int), foo(double, double), and foo(char, char), which does the same job but with different data types. Now, let’s say the company you work for has three different platforms on which this code runs, and they maintain three different codebases (read Git/SVN branches) for those three platforms. By platform, we mean the underlying hardware. Let’s say for one of its customers, your company uses Broadcom chipset (platform one). For a second customer, it uses the STMicroelectronics chipset (platform two). For the third customer, it uses a Samsung chipset (platform three). You found a bug in foo(int, int), and you fix it. A bug is a programmatic error in a code, and a fix is a solution for that, essentially a change in the source code. You realize that this fix applies to foo(double, double) and foo(char, char) also. So, you need to make changes in those two functions as well. The total number of changes you have to make in the source code is three per platform. As there are three different platforms, you have to port your fix to all those other places. Porting means to replicate the change(s) in all relevant platforms. So, you need to replicate one fix in three different places for one platform, and a total of nine places considerig three different platforms. Using templates, you can reduce that to only three changes instead of nine, just one fix per platform. And that’s a considerable gain. In a large project where there are millions of lines of code, the gain can be considerably significant. By fix we mean a modification in the source code which resolves a problem or bug.
  • The compiler does a type check of the arguments you pass while calling the template function. Template argument deduction does the type check while deducing the type of the passed arguments. Don’t be overwhelmed by the template syntax at this point. We will come back to that again in the next chapter. In this section, I brought this up just to explain some of the benefits of using templates. Consider the following code snippet:

type_conversion.cpp

#include <iostream>
int add(int x, int y)
{
    return x + y;
}
int main()
{
    std::cout << add(10, 20) << std::endl;
    std::cout << add(10, 20.2) << std::endl;
    return 0;
}

Compile the code as follows:

g++ type_conversion.cpp -o type_conversion

Run it using the following code:

./type_conversion

The output will be as follows:

30

30

As you will notice, in the previous program we have defined function add(), which takes two integers and returns the sum of the two integers. In the main() function, we have passed a pair of integers and then deliberately passed an integer and a double. The add() function is returning the result of both the additions, that is, 10 + 20 and 10 + 20.2 as 30. The second result is incorrect as adding 10 and 20.2, you should get 30.2 or you would perhaps anticipate an error in the second function call from the compile as the data types of the argument passed do not match with the function definition. The compiler didn’t complain. However, the program is returning the wrong output. So, what happened here?

This is due to a phenomenon called automatic type conversion by the compiler. The compiler is allowed to do type conversion of standard types if the types do not match between the parameter types in the definition and the argument types of the passed values in the function call. There are rules of implicit conversions and how the compiler applies them but that is out of scope for this book.

Here in our case, the compiler looks into the function definition of the add() function and says, “Okay, I have a function that takes two integers, but the user has passed an integer and a double! So, I convert the double to an integer type so that it fits in the parameter type of the function”. Yes, compilers can do that. If you inadvertently pass the wrong type of data while calling the function, you may get an unexpected result. Now, let’s convert our add() function into a template function as follows:

type_conversion_template_simple.cpp

#include <iostream>
template <typename T>
T add(T x, T y)
{
    return x + y;
}
int main()
{
    std::cout << add(10, 20) << std::endl;
    return 0;
}

Compile and run the code. You will see the output is 30:

30

Now let’s change the code to replace the second argument to add() to a double as follows:

type_conversion_template.cpp

#include <iostream>
template <typename T>
T add(T x, T y)
{
    return x + y;
}
int main()
{
    std::cout << add(10, 20.5) << std::endl;
    return 0;
}

If you try to compile this program, it will fail in the compilation. Let’s have a look why. Look at the following compilation error:

$ g++ type_conversion_template.cpp -o type_conversion_template
type_conversion_template.cpp: In function 'int main()':
type_conversion_template.cpp:10:30: error: no matching function for call to 'add(int, double)'
     std::cout << add(10, 20.5) << std::endl;
                              ^
type_conversion_template.cpp:3:3: note: candidate: template<class T> T add(T, T)
 T add(T x, T y)
   ^~~
type_conversion_template.cpp:3:3: note:   template argument deduction/substitution failed:
type_conversion_template.cpp:10:30: note:   deduced conflicting types for parameter 'T' ('int' and 'double')
     std::cout << add(10, 20.5) << std::endl;
                              ^ 

This error message tells you that the compiler tried to deduce the arguments passed to the add() function and found conflicting types between the template parameters definition and what have been passed in the function template call. No implicit type conversion is allowed in template argument deduction, and hence in this case you get an error message from the compiler. Even if you pass a wrong argument type while calling the template function, the compiler will catch that error. This works to the benefit of the programmer that they come to know about the inadvertent mistake during compile time rather than inconsistent behavior in runtime. This brings us to the next topic: using templates is preferred over function overloading in a similar situation.

Function overloading versus templates

“It is easy to study the rules of overloading and of templates without noticing that together they are one of the keys to elegant and efficient type-safe containers.”

— Bjarne Stroustrup

Function overloading is a feature in C++ that is not available in languages such as C. It allows the programmer to write functions that have the same name but different parameter sets, that is, a different number of arguments or arguments with different data types. Function overload is very similar to what we have been discussing about templates. Then why do we need templates? Function overloading comes in handy when we want to achieve different operations with the same function name but with varying arguments. Let us have a look at the following example code:

function_overloading.cpp

#include <iostream>
#include <string.h>
size_t size_of(int x)
{
    return sizeof(x);
}
size_t size_of(double x)
{
    return sizeof(x);
}
size_t size_of(const char *str)
{
    return strlen(str);
}
int main()
{
    std::cout << size_of(10) << std::endl;
    std::cout << size_of(4.2) << std::endl;
    std::cout << size_of("Hello World!") << std::endl;
    return 0;
}

Compile and run the previous program as follows:

$ g++ function_overloading.cpp -o function_overloading
$ ./function_overloading

The following will be the output:

4
8
12

In the previous program, we overloaded a function size_of() that operates on int, double, and string of character type arguments and returns the argument’s size. The function’s name remains the same for the application developer, but the argument type varies. When the argument type is int or double, we are returning the length of the argument using the inbuilt sizeof() operator. However, when the argument type is of a pointer (character string), we have used another library function strlen() to determine its length. In function overloading, we may have different function body (programming logic) for different argument sets but the same function name.

In templates, we have the same function body and name but different argument types. Remember, function overloading is also possible if the function body remains the same. But in that case, to get rid of the repetitive code, you must use the template.

In addition to these points, remember that using templates provides extra type checking which function overloading doesn’t provide. Let’s have a look at the following example:

func_overloading_type_conversion.cpp

#include <iostream>
int foo(int x, int y)
{
    return x + y;
}
double foo(double x, double y)
{
    return x + y;
}
int main()
{
    std::cout << foo(10, 20) << std::endl;
    std::cout << foo(10.2, 20.3) << std::endl;
    std::cout << foo(10, ‘A’) << std::endl;
    return 0;
}

We have overloaded a function foo() with a pair of integers and with a pair of doubles. Now in the main function, we have first passed two integers to foo(), next we passed two doubles to foo(), and finally, we have passed an integer and a character to foo(). Now if you compile and run this program, you will see an output like this:

$ g++ func_overloading_type_conversion.cpp -o func_overloading_type_conversion
$ ./func_overloading_type_conversion
30
30.5
75

foo() function has returned 75 as a result of the addition of the characters A and integer 10. Notice that we do not have a version of foo() which accepts an integer and character, but the compiler didn’t give any error. And that is due to implicit promotion of the input char to int (ASCII value of ‘A’ is 65) by the compiler. However, this may not necessarily be the intention of the programmer. Let’s rewrite our foo() function as a template function:

function_template_type_check.cpp

#include <iostream>
template<typename T>
T foo(T x, T y)
{
    return x + y;
}
int main()
{
    std::cout << foo(10, 20) << std::endl;
    std::cout << foo(10.2, 20.3) << std::endl;
    std::cout << foo(10, 'A') << std::endl;
    return 0;
}

Now if we compile this program, it will show the following error:

$ g++ function_template_type_check.cpp -o function_template_type_check
function_template_type_check.cpp: In function 'int main()':
function_template_type_check.cpp:13:29: error: no matching function for call to 'foo(int, char)'
     std::cout << foo(10, 'A') << std::endl;
                             ^
function_template_type_check.cpp:4:3: note: candidate: template<class T> T foo(T, T)
 T foo(T x, T y)
   ^~~
function_template_type_check.cpp:4:3: note:   template argument deduction/substitution failed:
function_template_type_check.cpp:13:29: note:   deduced conflicting types for parameter 'T' ('int' and 'char')
     std::cout << foo(10, 'A') << std::endl;
                             ^

The compiler has spotted the mismatch in data types in the function template definition and the argument types passed in the call. So evidently, template functions guarantee to detect accidental wrong parameter passed to it, but function overloading does not. Our next candidate, which can come in handy in dealing with different types of argument types, is macro. A macro is a piece of code that is identified by a name. Once defined, the macro name can be used anywhere in the program. We can also pass arguments to macros. Whenever the macro name appears in the program, the preprocessor replaces it with the piece of code which the macro stands for. Remember, macros are blind code substitution carried out by the preprocessor. The compiler cannot see the macro names because, at the beginning of the compilation process, the preprocessor processes the complete source code and replaces the macro references by their actual body, that is, the piece of code the macro name represents. Once the preprocessor finishes its work, then the pre-processed code is handed over to the compiler. Macros are defined with the keyword define preceded by the # operator. For example, I may like the name PACKT to stand for the string PACKT Publishing in my program. To achieve that I may define a macro like the following in my program:

#define PACKT “PACKT Publishing”

Now when we compile the program first, the pre-processor will process the program. From the above definition of macro, the pre-processor knows that the name PACKT has to be replaced by the string PACKT Publishing. So that is a very brief introduction to macros. We will see more examples of macros in the following section. Macros can take any data type as input. In that sense, macros can be pitted against the templates. In the next section, we will try to find out how fair templates do when pitted against the macros.

Macro versus templates

Macros can be deemed as a mechanism to pass any data type to achieve the same goal as using templates. Let’s look at the following example:

macros_example.cpp

#include <iostream>
#define add(x, y) (x + y)
int main()
{
    std::cout << add(10, 20) << std::endl;
    std::cout << add(4.2, 5.8) << std::endl;
    return 0;
}

Here, we have defined a macro add()and from the main() function we have called the macro twice: first with a pair of integers and then with a pair of doubles. Now, if you compile and run the program you will see an output like this:

30
10

As you can see, our previously defined macro add() just worked fine with a pair of integers as well as a pair of doubles. Macros are another way to eliminate repetitive code with different data types.

Note: Macros are processed by the pre-processor and not by the compiler, hence there is no error checking at all.

Then why do we need templates? Firstly, carelessly written macros may have side effects. Let’s have a look at the following piece of code:

macros.cpp

#include <iostream>
#define multiply(x, y)  (x * y)
int main()
{
    std::cout << multiply(10, 20) << std::endl;
    std::cout << multiply(10 + 10, 20 + 20) << std::endl;
    return 0;
}

In this program, the macro multiply() is intended to take two numbers and return the value of their multiplication. If you compile and run the program you will see the output as follows:

200
230

The output from the first call to multiply() is correct. But in the second call to multiply(), we have passed two arguments, 10 + 10, and 20 + 20. The output of this operation should be 800, but the macro multiply() generated 230, which is incorrect. So, what has just happened here?

Macros work on a text substitution principle. When we are passing an argument to the macro, the arguments are blindly substituted by the preprocessor into the macro body. When we call our multiply() macro with (10 + 10, 20 + 20), the arguments are substituted into the macro body multiply() as shown in the following pictorial representation:

Figure 1.2 – Side effect of incorrectly defined macro

To avoid this problem, parameters in the macro definition are embraced in brackets. Let’s have a look at the modified version of our multiply macro:

macros_modified.cpp

#include <iostream>
#define multiply(x, y)  ((x) * (y))
int main()
{
    std::cout << multiply(10, 20) << std::endl;
    std::cout << multiply(10 + 10, 20 + 20) << std::endl;
    return 0;
}

Now, if you compile and run the program, you will see the correct output:

200
800

Here’s a pictorial representation of the effect of this modification is as shown next:

Figure 1.3 – Correct way of defining macros

So improper implementation of macros can lead to tricky bugs in the code. The second downside of the macro is that it may lead to a significant increase in the code size. Because of the way macros are designed, wherever we call a macro it is substituted by the macro body and hence the whole macro body is inserted in the code by the preprocessor. Because the same macro is pasted multiple times the code size increases significantly. The third problem with macro is that it is processed by the preprocessor and hence there is no type checking. Let us again have a look at the add() the following code:

macros_typecheck.cpp

#include <iostream>
#define add(x, y) (x + y)
int main()
{
    std::cout << add(10, 5.2) << std::endl;
    return 0;
}

And the following is the output:

15.2

In the previous program, we passed an integer and a double to the macro add() and we got a result of 15.2, which is correct. When we call the macro with the arguments (int and double), that gets substituted in the macro body. The compiler sees we are trying to add an integer with a double. Compiler implicitly converts the integer into a double and performs the addition. And, we get the output of 15.2. This looks benign and correct. However, the programmer may not always intend this. The intention may have been to use the add() macro to add strictly two integers. But if we pass a double accidentally, it will happily substitute the arguments passed and leave it to the compiler’s discretion to decide if there is any implicit type conversion required or not. Because of these issues, macros are not really a substitute for templates. In the next section, we are going to explore some more salient characteristics of function templates.

Important facts about templates

There are aspects of function templates that need further emphasis. Let’s learn about some unique characteristics of function templates in the following sections.

No Template code generated if not used

The compiler doesn’t generate any code for a template if the template hasn’t been used in the program. Templates only represent a design of a function or a class with the generic data type. Because templates are defined using generic data types, the compiler does not understand how to interpret the data type from the template definition itself. That’s why the compiler cannot even compile templates unless an actual instance is created with the actual data type, a process known as template instantiation. Let’s look at the following example code:

template_code_generation.cpp

#include <iostream>
template<typename T>
T add(T x, T y)
{
    return x + y;
}
int main()
{
    std::cout << "Hello World" << std::endl;
    return 0;
}

In this program, we have defined a template function called add() but nowhere in the main() function, we have called add() function template. Compile the code and generate the binary executable as shown:

g++ template_code_generation.cpp -o template_code_generation

We have named the binary executable template_code_generation. To check if any code has been generated for the template add(), you can use a Linux tool called strings. Let us have a look:

$ strings template_code_generation | grep add

The tool strings, when operated on a binary file prints all the available strings in the file. Here, we have passed our binary file template_code_generation to the strings tool and searched (grep) the string add, but nothing was returned. That means there is no string named add in template_code_generation binary file. Now, let us search (grep) main as follows:

$ strings template_code_generation | grep main
__libc_start_main

As you can see, the string main, is there in the binary executable but add is not. This means the function main() has been compiled and binary code has been generated by the compiler. But no binary code has been generated for the template function add() as we have not called it in the program.

Note: No binary or machine language code is generated for function templates if the function template has not been called in the program.

However, the same is not true for non-template functions. For example:

function_code_generation.cpp

#include <iostream>
int add(int x, int y)
{
    return x + y;
}
int main()
{
    std::cout << "Hello World" << std::endl;
    return 0;
}

We have defined a function add in this program but haven’t used it in main(). Now, if compile and search for the function name string add() in the binary executable, we will see the following:

$ g++ function_code_generation.cpp -o function_code_generation
$ strings function_code_generation | grep -i add
_GLOBAL__sub_I__Z3addii

As you can see, add() function is there in the binary executable. Notice that the name of add() function has been changed by the compiler. This is because of the name mangling. Name mangling means C++ compiler renames the name of the functions during compilation to support function overloading (multiple functions with the same name). We are discussing everything almost in terms of function templates. And that is because function templates are simpler and easy to understand. In Chapter 6, Class Template, we will discuss class templates and various aspects of them. However, the rules of the template are mostly the same for both function and class templates.

Template instantiation

Templates are just a pattern given to the compiler to generate multiple functions or classes of the same functionality but with different data types. They have no real existence beyond the point that they tell the compiler how a particular entity (read function or class) will look in the future. When a template is called in the program, the compiler looks at the template definition and then looks at the argument types passed. If those arguments agree with template parameter types, then the compiler generates a function or class with that specific argument type. This process is called template instantiation. Instantiated functions or classes are like any manually written function or class, and there is nothing special about them.

Figure 1.4 – Conceptual model of template instantiation

As shown in the previous conceptual model, the template function add() has been called with two integers, and the compiler has instantiated an instance of add() with two integer type parameters, which the programmer could have manually written. There is no difference between the instance the compiler generates and a manually written function.

No error checking until instantiation

The compiler does not perform error checking of template function code if it has not been called in the program. If you do not call a template function, even if it has any programmatic error, the compiler will not report that; however, if there is any syntactical error, that will be checked and reported by the compiler. Let us have a look at the following program:

error_checking_template.cpp

#include <iostream>
struct mystruct {
    int a;
};
template<typename T>
void foo(T& s)
{
    std::cout << s.a << std::endl;
    std::cout << s.b << std::endl;
}
int main()
{
    struct mystruct s{10};
    return 0;
}

In the previous code, we have defined a structure mystruct, which has an integer type member variable a. Then we have defined a template function foo, which accepts a reference to a user defined data type and then prints two member variables of that input data type namely a and b. However, in the main program, we have not called the template function. Now, if we try compile and run the program, there will be no error thrown by the compiler as shown next:

$ g++ error_checking_template.cpp -o error_checking_template

As you might have noticed, the structure does not have any member called b. This is a programmatic error. However, the compiler didn’t throw any error. The reason is unless the template is called (instantiated) in the program, the compiler does not do any error check in the template body. Now to verify this, add the following line in the main() function body just before the return statement:

foo(s);

And then try compile the program again and you should see the following errors:

$ g++ error_checking_template.cpp -o error_checking_template
error_checking_template.cpp: In instantiation of 'void foo(T&) [with T = mystruct]':
error_checking_template.cpp:18:10:   required from here
error_checking_template.cpp:11:20: error: 'struct mystruct' has no member named 'b'
     std::cout << s.b << std::endl;

Type checking

The compiler checks the types of arguments we pass while calling a template function against the parameter’s types in the template definition. If the arguments’ type matches, then the compiler instantiates the template into an ordinary function. If the types do not match, then the compiler reports an error. The conceptual model of the same is shown as follows:

Figure 1.5 – Conceptual model Type Check

Memory usage with templates

It is important to remember that memory usage is not enhanced by using templates. If we define a function template and then call the template by passing three different data types, the compiler generates three different versions of the template. This is precisely the same if three different versions were written manually. So, the memory required to hold the three copies of the generated functions would be the same in both cases: if you use a template or write the functions with three different data types manually.

Summary

We started the chapter by introducing you to the brief history of when the templates came into picture in C++ language and we learned why we should care about templates, their benefits, and how they can enhance our coding. Then we looked into the other options such as function overloading and macros, which may appear to be doing the same job as templates do, and how templates are different from those. Finally, we touched upon some salient features of templates. By the end of this chapter, you should have the basic ideas about templates, and you should also be able the appreciate the importance of templates at a high level. The next chapter will focus on and understand the syntax of function templates and their implementation.

Questions

  1. What is template?
  2. What are the benefits of using templates?
  3. What is template instantiation?
  4. What is type checking of template parameters?

Answers

  1. Templates are the blueprint of a generic function or class which can work with different types of data.
  2. Please refer to the summary section for the advantages.
  3. When we pass arguments to the function template, the compiler generates the actual function definition with the passed arguments’ data type(s). That means an instance of the template function is generated. This is called template instantiation. The same is true for class templates as well.
  4. When we call a function template with a set of arguments, the compiler cross-verifies the arguments’ data types and the template function parameters. If there is a mismatch, the compiler throws an error. This is called type checking.

Leave a Reply