Templates allow us to write generic classes (called template classes) and generic functions(called template functions) that can work with generic data-types.
It is the responsibility of the compiler to create their specialized forms during the compilation step. Keywords template, typename and class are used to create templates in C++;
A template class deals with template parameters whereas a template function deals with both template parameters and function parameters.
| /* Template Function. 'template <class T>' or 'template <typename T>' | |
| can be used here.*/ | |
| template <class T> | |
| void exampleFunction(T val) { | |
| } | |
| /* Template Class. 'template <class T>' or 'template <typename T>' | |
| can be used here.*/ | |
| template <class T> | |
| class ExampleClass { | |
| }; | |
| int main() { | |
| exampleFunction<int>(5); //Here 'int' is a template argument and '5' is a function argument | |
| ExampleClass<int> ec1;//Here 'int' is a template argument | |
| return 0; | |
| } |
In case of a template function with parameters, compiler can deduce the data-types based on the arguments passed to this function. But in case of a template function with no parameters as well as a template class, the data-types must be explicitly specified during function invocation and during the creation of the class instance respectively.
| #include <iostream> | |
| using namespace std; | |
| /* Template Function. 'template <class T>' or 'template <typename T>' | |
| can be used here.*/ | |
| template <class T> | |
| void exampleFunction(T val) { | |
| cout << val; | |
| } | |
| /* Template Class. 'template <class T>' or 'template <typename T>' | |
| can be used here.*/ | |
| template <class T> | |
| class ExampleClass { | |
| T memberVariable; | |
| public: | |
| ExampleClass(T val) :memberVariable(val) {} | |
| void printMemberVaraible() { | |
| cout << memberVariable; | |
| } | |
| }; | |
| int main() { | |
| exampleFunction(5); //correct | |
| exampleFunction<int>(5); //correct. 'int' type passed as template argument is optional; | |
| ExampleClass ec1(4); //incorrect. 'int' Type needs to passed as a template argument. | |
| ExampleClass<int> ec1(4);//correct | |
| return 0; | |
| } |
It is possible to pass type arguments (int,string etc) as well as non-type arguments (3,”hello” etc) as template parameters. Non-type arguments are usually passed in order to maintain state information. Non-type arguments must not be variables but constants as they need to be known at compile time.
| #include <iostream> | |
| using namespace std; | |
| template <int min, int max> | |
| class ExampleClass { | |
| }; | |
| int main() { | |
| /*Incorrect usage of non-type parameters. They should be known at compile time.*/ | |
| int min1, max1; | |
| cin >> min1; cin >> max1; | |
| ExampleClass<min1, max1> ec1; | |
| /*Correct Usage non-type parameters.*/ | |
| ExampleClass<0, 10> ec2; | |
| return 0; | |
| } |
Template Functions
Say we need to write a sort method that works with integer type, float type, char type, string type etc. We can write a separate sort() method for each of these types. But that is cumbersome and creates a lot of redundant code. It is in such scenarios that template functions come to our rescue. We can write a single sort() method that accepts a generic type as its argument. Now, its up to the compiler to create the specialized forms of this generic function if needed in the program.
| #include <iostream> | |
| #include <vector> | |
| #include <string> | |
| using namespace std; | |
| template <typename T> | |
| void sort(vector<T>& array) { | |
| int i = 0; | |
| while (i < array.size()) { | |
| for (int j = 0; j < array.size() - 1-i; j++) { | |
| if (array[j] > array[j+1]) { | |
| T temp = array[j]; | |
| array[j] = array[j + 1]; | |
| array[j + 1] = temp; | |
| } | |
| } | |
| i++; | |
| } | |
| } | |
| int main() { | |
| // int array | |
| vector<int> intArray = {4,3,6,1}; | |
| sort(intArray); | |
| for (int each : intArray) cout << each << " "; | |
| cout << endl; | |
| // char array | |
| vector<char> charArray = {'b','r','w','p'}; | |
| sort(charArray); | |
| for (char each : charArray) cout << each << " "; | |
| cout << endl; | |
| //string array | |
| vector<string> stringArray = { "hello","how","are","you" }; | |
| sort(stringArray); | |
| for (string each : stringArray) cout << each << " "; | |
| cout << endl; | |
| cin.get(); | |
| return 0; | |
| } |
Output:
1 3 4 6 b p r w are hello how you
It is also possible to create our own specializations of a given template function. For example, consider the previous example. But there is a new requirement that if the input type is a string, sort() should perform sort in descending order. The rest of the requirement remain the same.
| #include <iostream> | |
| #include <vector> | |
| #include <string> | |
| using namespace std; | |
| template <typename T> | |
| void sort(vector<T>& array) { | |
| int i = 0; | |
| while (i < array.size()) { | |
| for (int j = 0; j < array.size() - 1-i; j++) { | |
| if (array[j] > array[j+1]) { | |
| T temp = array[j]; | |
| array[j] = array[j + 1]; | |
| array[j + 1] = temp; | |
| } | |
| } | |
| i++; | |
| } | |
| } | |
| template <> | |
| void sort (vector<string>& array) { | |
| int i = 0; | |
| while (i < array.size()) { | |
| for (int j = 0; j < array.size() - 1 - i; j++) { | |
| if (array[j+1] > array[j]) { | |
| string temp = array[j]; | |
| array[j] = array[j + 1]; | |
| array[j + 1] = temp; | |
| } | |
| } | |
| i++; | |
| } | |
| } | |
| int main() { | |
| // int array | |
| vector<int> intArray = {4,3,6,1}; | |
| sort(intArray); | |
| for (int each : intArray) cout << each << " "; | |
| cout << endl; | |
| // char array | |
| vector<char> charArray = {'b','r','w','p'}; | |
| sort(charArray); | |
| for (char each : charArray) cout << each << " "; | |
| cout << endl; | |
| //string array | |
| vector<string> stringArray = { "hello","how","are","you" }; | |
| sort(stringArray); | |
| for (string each : stringArray) cout << each << " "; | |
| cout << endl; | |
| cin.get(); | |
| return 0; | |
| } |
Output:
1 3 4 6 b p r w you how hello are
Template Classes
In the example below, the first template parameter is a non-type parameter whereas the second template parameter is a type parameter. As you can see, it is also possible to provide default values to the template parameters.
| #include <iostream> | |
| using namespace std; | |
| template <int val = 3, class T = int> | |
| struct Example { | |
| int multiplier = val; | |
| T func(T a) { | |
| a *= multiplier; | |
| return a; | |
| } | |
| }; | |
| int main() { | |
| Example <> e1; | |
| Example <5> e2; | |
| Example <2, float> e3; | |
| cout << e1.func(1) << endl; | |
| cout << e2.func(4) << endl; | |
| cout << e3.func(2.4) << endl; | |
| cin.get(); | |
| return 0; | |
| } |
Output:
3 20 4.8
While creating a template class, we can either put the class definition and the implementation in a single header file(.h) or we can keep the class definition in a header file(.h) and the implementation in a separate .cpp file. But in the latter case, the .cpp file should also contain the following piece of code for each type that might be used with the template class.
| template class class_name <type>; |
Variable Templates
C++14 has introduced a new type of templates called as variable templates. Please go through this article to learn more about them.
Leave a comment