“Don’t let the noise of other’s opinion drown out your own inner voice” Steve Jobs
There are times that we want to run the same task on multiple different data-types.
One way to do this is by creating multiple functions, each takes a list of arguments but with different data-types and all these functions do the same task.
Example:
#include <iostream>
using namespace std;
void sum(double a, double b){
cout<<"Double-type function"<<endl;
cout<< a +b<<endl;
}
void sum(int a , int b){
cout<<"Integer-type function"<<endl;
cout<<a +b<<endl;
}
int main() {
sum(2,3);
sum(2.4, 34.22);
return 0;
}
Output:
Integer-type function
5
Double-type function
36.62
Note: please check the function overloading section if you don’t know how we used a function’s name twice.
In the example above, we’ve overloaded the same function sum() two times to be able to work with integer and float numbers as the arguments.
Now let’s refactor the source code above, but this time we will use function-templates:
#include <iostream>
using namespace std;
template<typename T>
void sum(T a , T b){
cout<<a+b<<endl;
}
int main() {
sum(2,3);
sum(2.4, 34.22);
return 0;
}
Output:
5
36.62
In the example above, the data-type of the parameters within sum() function is something we made up via typename keyword (could be anything else like Jack, Ellen etc.). This data-type name is simply a placeholder.
When the call to the sum() function happened within the body of the main() function, behind the scene the compiler will check the data-type of the arguments in each call to this function and based on that, it will generate a function-definition that has the structure and instructions of the sum function-temple and the correct data type that matches the arguments and it will use this generated function to handle the call.
So in this example the first call to the sum() function was:
sum(2,3);
And because the type of these two numbers is int, behind the scene the compiler uses the template function to generate a function definition that is equal to this:
void sum( int a, int b){
cout<< a + b<<endl;
}
Also the next time the call to the sum() function happened, again the compiler checked the data-type of the arguments and realized that this time we called the function with values of type double. Here the compiler generates another function-definition but this time the type of parameters is double.
void sum( double a, double b){
cout<< a + b<<endl;
}
As you can see, with function-template we can simply create a template with all sorts of tasks that needs to be done within the body of the template and allow the compiler to automatically generate function-definitions as many as necessary.
When we call a function-template in the source-code and put values as its argument, behind the scene the compiler will automatically check the type of these values, and it will generate a function definition based on the function-template and the data-type of the input values.
Just like functions, function-templates have two parts:
-
Template-declaration
-
Template- definition
Note: if we put the Template-definition on top of the main() function, there’s no need to declare the template-declaration as well.
Function-Template declaration:
This is simply the prototype of the function-template:
template <typename name> data-type function-name (data-type a, data-type n);
`template`: in order to create a function-template, we first start with the keyword `template`.
`<>`: followed by the keyword `template` we use angle brackets `<>` and within it, we set one or more type-names.
`typename`: we use the `typename` keyword to create names that can be used instead of the real data-types of parameters as well as return data-type of the function.
Note: instead of the name typename we could also use the keyword class as well.
For example: template <typename Jack> void sum( Jack a, Jack b);
Now when we call the sum function with arguments, compiler will first check the data-type of the arguments. Then it will generate a function-definition from this template and replace each input’s data type with the names we used for data-types of the parameters.
For example, let’s say we called the sum() function with the values of 10 and 15 like: sum(10,15);.
Now the compiler realizes that the data-type of the first argument is int and it will link the int with Jack.
This means in the generated function, all those data-types that are named Jack will be replaced with int data-type.
Note: in case of `sum()` function, if we put two arguments in the function, each of different data-type, we will get a compile time error because we used one name `Jack` for both parameters and now the data-type of the first argument is considered as the replacement of the name `Jack` but the second argument is also trying to use a different data-type for this name and that's why the compiler will return error.
Example:
#include <iostream>
using namespace std;
template<typename T>
void sum(T a , T b){
cout<<a+b<<endl;
}
int main() {
sum(2,3.55);
return 0;
}
Output:
error: no matching function for call to ‘sum(int, double)’
sum(2,3.55);
Here, when we called the sum() function, we put two arguments of type int and double respectively. But in the function-template for both parameters we used the same name. And now because there’re two data-types for one name, the compiler retuned error.
`data-type`: data-type can be set to either the name created via `typename` keyword or we can explicitly set a known data-type for a parameter or the return data-type of the function.
Note: we could also create two or more names via `typename` keyword and use each one as the placeholder for parameter's data-type.
Example:
#include <iostream>
using namespace std;
template<typename T, typename S>
void sum(T a , S b){
cout<<a+b<<endl;
}
int main() {
sum(2,3.55);
return 0;
}
Output: 5.55
In this example, the function-template named sum, has two parameters each with different name for its data-type. So here we’re allowed to use two values each with different data-type when calling the sum function, and behind the scene the compiler will generate a function-definition that replaces the first data-type of the first parameter with the data-type of the first argument and the data-type of the second parameter with the data-type of the second argument.
Basically the generated function would be like this when the call to the sum function happened:
void sum(int a , double b){…}
Function-Template- definition:
The compiler will generate functions from our function-template and for the body of the generated function (including tasks that should be done within this body) it will use the body of the function-template-definition.
The same structure of the template-declaration applies to template-definition + the body in which we can use to write the tasks that should be done. (These tasks will be copied to the generated function-definition)
Example:
template (typename T)void sum (T a, T b){
//body of the function-template
}
Overloaded Templates:
Just like functions, function-templates can also be overloaded. In this case, based on the type of arguments we put when calling a function-template, behind the scene the compiler will check all the overloaded function-templates and deduce which one to choose in order to generate the function-definition from.
Example:
#include <iostream>
using namespace std;
template <typename T>
void sum (T a, T b){
cout<< "message from T"<<endl;
}
template <typename A>
void sum(A *a, A *b){
cout<< "message from A"<<endl;
}
int main() {
sum(1,2);
int i = 100;
int b = 200;
sum(&i, &b);
return 0;
}
Output:
message from T
message from A
In the example above, we have two function-templates named sum. But one of them accepts actual values (which can be integer values, double, objects etc.) but the other one accepts the memory address of these types of values.
Note: check the pointers section to learn more about memory address.
So whenever we call the sum function with basic data-types (Just like the first time the sum function is called in the body of the main() function) the first function-template will be used to generate the function-definition and whenever we call the sum function and assign memory-addresses as its arguments (Just like the second time the sum function is called in the body of the main() function), the second function-template will be called to generate the function-definition.
Explicit Template Specialization:
Consider the template-function named sum that we used in the previous examples. The structure of the template was template <typename T> void sum(T a, T b). This template basically accepts two arguments of any type.
With the help of explicit-template-specialization we can overload the sum template and create another one that will be used only when arguments of specific data-type is set when calling the sum() function.
Example:
#include <iostream>
using namespace std;
template <typename T>
void sum (T a, T b){
cout<< "message from T"<<endl;
}
template<>
void sum<char>(char a, char b){
cout<<"message from char"<<endl;
}
int main() {
sum(1,2);
int i = 100;
int b = 200;
sum('a','b');
return 0;
}
Output:
message from T
message from char
As you can see, when we overloaded the sum function-template, we used ` template<>
void sum<char>(char a, char b)`.
In order to create Template-Specialization:
-
After the keyword `template` we use empty angle
brackets.
Note: this is because the data-types for parameters and returned data-type should be explicitly declared.
-
After the name of the template-function we use angle-brackets and
within it, we declare the target data-type that this function-template
should be used for. -
Of course the data-type of the parameters now should be the same
as the declared data-type in the angle-brackets.
From now on, any time the sum() function is called and the data-type of its arguments was char, the Explicit-Template-Specialization will be the target template that the compiler will use as the function-definition.
And that’s how the second time we called the sum() within the body of the main() function, because the data-type of the arguments was char, we got the output that is coming from the sum explicit-specialization’s body.
Explicit-Instantiation:
When working with Template-functions, it’s the compiler that will deduce what data-type it should use when generating the function-definition of a template-function.
But when calling a function-template and assigning arguments to the function, we can explicitly tell the compiler what data-type it should use when generating the function-definition. This is called Explicit-Instantiation.
Example:
#include <iostream>
using namespace std;
template<typename T, typename S>
void sum(T a , S b){
cout<<a+b<<endl;
}
int main() {
sum<int, float>(2,3.55);
return 0;
}
Output:
5.5
Within the body of the main() function, we called the sum() but this time we used explicit instantiation by adding the angle brackets after the name sum and putting the data-type for the parameters respectively.
Note: here because each parameter had different name (created via typename keyword) in the function-template, we needed to set two data-types each separated via comma. If there was only one name set for all the parameters in the function-template, then in that case we could only use one data-type in the angle brackets.