Section 5.1 Templates | ||
Templates are a new feature introduced by ANSI-C++ standard. If
you use a C++ compiler that is not adapted to this standard it is possible
that you cannot use them. |
template <class identifier> function_declaration;the only difference between both prototypes is the use of keyword class or typename, its use is indistinct since both expressions have exactly the same meaning and behave exactly the same way.
template <typename identifier> function_declaration;
For example, to create a template function that returns the greater one of two objects we could use:
As the first line specifies, we have created a template for a generic data type that we have called GenericType. Therefore in the function that follows, GenericType becomes a valid data type and it is used as the type for its two parameters a and b and as the return type for the function GetMax.template <class GenericType> GenericType GetMax (GenericType a, GenericType b) { return (a>b?a:b); }
GenericType still does not represent any concrete data type; when the function GetMax will be called we will be able to call it with any valid data type. This data type will serve as a pattern and will replace GenericType in the function. The way to call a template class with a type pattern is the following:
function <pattern> (parameters);Thus, for example, to call GetMax and to compare two integer values of type int we can write:
int x,y;so GetMax will be called as if each appearance of GenericType was replaced by an int expression.
GetMax <int> (x,y);
Here is the complete example:
// function template #include <iostream.h> 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; } |
6 10 |
(In this case we have called the generic type T instead of GenericType because it is shorter and in addition is one of the most usual identifiers used for templates, although it is valid to use any valid identifier).
In the example above we used the same function GetMax() with arguments of type int and long having written a single implementation of the function. That is to say, we have written a function template and called it with two different patterns.
As you can see, within our GetMax() template function the type T can be used to declare new objects:
T result;result is an object of type T, like a and b, that is to say, of the type that we enclose between angle-brackets <> when calling our template function.
In this concrete case where the generic T type is used as a parameter for function GetMax the compiler can find out automatically which data type is passed to it without having to specify it with patterns <int> or <long>. So we could have written:
int i,j;since both i and j are of type int the compiler would assume automatically that the wished function is for type int. This implicit method is more usual and would produce the same result:
GetMax (i,j);
// function template II #include <iostream.h> 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; } |
6 10 |
Notice how in this case, within function main() we called our template function GetMax() without explicitly specifying the type between angle-brackets <>. The compiler automatically determines what type is needed on each call.
Because our template function includes only one data type (class T) and both arguments it admits are both of that same type, we cannot call our template function with two objects of different types as parameters:
int i;This would be incorrect, since our function waits for two arguments of the same type (or class).
long l;
k = GetMax (i,l);
We can also make template-functions that admit more than one generic class or data type. For example:
In this case, our template function GetMin() admits two parameters of different types and returns an object of the same type as the first parameter (T) that is passed. For example, after that declaration we could call the function by writing:template <class T, class U> T GetMin (T a, U b) { return (a<b?a:b); }
or simplyint i,j; long l; i = GetMin<int,long> (j,l);
even though j and l are of different types.i = GetMin (j,l);
The class that we have just defined serves to store two elements of any valid type. For example, if we wanted to declare an object of this class to store two integer values of type int with the values 115 and 36 we would write:template <class T> class pair { T values [2]; public: pair (T first, T second) { values[0]=first; values[1]=second; } };
pair<int> myobject (115, 36);this same class would also serve to create an object to store any other type:
pair<float> myfloats (3.0, 2.18);The only member function has been defined inline within the class declaration. If we define a function member outside the declaration we must always precede the definition with the prefix template <... >.
// class templates #include <iostream.h> template <class T> class pair { T value1, value2; public: pair (T first, T second) {value1=first; value2=second;} T getmax (); }; template <class T> T pair<T>::getmax () { T retval; retval = value1>value2? value1 : value2; return retval; } int main () { pair <int> myobject (100, 75); cout << myobject.getmax(); return 0; } | 100 |
template <class T>All Ts that appear are necessary because whenever you declare member functions you have to follow a format similar to this (the second T makes reference to the type returned by the function, so this may vary).
T pair<T>::getmax ()
// Template specialization #include <iostream.h> template <class T> class pair { T value1, value2; public: pair (T first, T second) {value1=first; value2=second;} T module () {return 0;} }; template <> class pair <int> { int value1, value2; public: pair (int first, int second) {value1=first; value2=second;} int module (); }; template <> int pair<int>::module() { return value1%value2; } int main () { pair <int> myints (100,75); pair <float> myfloats (100.0,75.0); cout << myints.module() << '\n'; cout << myfloats.module() << '\n'; return 0; } |
25 0 |
As you can see in the code the specialization is defined this way:
template <> class class_name <type>The specialization is part of a template, for that reason we must begin the declaration with template <>. And indeed because it is a specialization for a concrete type, the generic type cannot be used in it and the first angle-brackets <> must appear empty. After the class name we must include the type that is being specialized enclosed between angle-brackets <>.
When we specialize a type of a template we must also define all the members equating them to the specialization (if one pays attention, in the example above we have had to include its own constructor, although it is identical to the one in the generic template). The reason is that no member is "inherited" from the generic template to the specialized one.
// array template #include <iostream.h> template <class T, int N> class array { T memblock [N]; public: void setmember (int x, T value); T getmember (int x); }; template <class T, int N> array<T,N>::setmember (int x, T value) { memblock[x]=value; } template <class T, int N> T array<T,N>::getmember (int x) { return memblock[x]; } int main () { array <int,5> myints; array <float,5> myfloats; myints.setmember (0,100); myfloats.setmember (3,3.1416); cout << myints.getmember(0) << '\n'; cout << myfloats.getmember(3) << '\n'; return 0; } |
100 3.1416 |
It is also possible to set default values for any template parameter just as it is done with function parameters.
Some possible template examples seen above:
template <class T> // The most usual: one class parameter. template <class T, class U> // Two class parameters. template <class T, int N> // A class and an integer. template <class T = char> // With a default value. template <int Tfunc (int)> // A function as parameter.
When projects grow it is usual to split the code of a program in different source files. In these cases, generally the interface and implementation are separated. Taking a library of functions as example, the interface generally consists of the prototypes of all the functions that can be called. These are generally declared in a "header file" with .h extension, and the implementation (the definition of these functions) is in an independent file of c++ code.
The macro-like functionality of templates, forces a restriction for multi-file projects: the implementation (definition) of a template class or function must be in the same file as the declaration. That means we cannot separate the interface in a separate header file and we must include both interface and implementation in any file that uses the templates.
Going back to the library of functions, if we wanted to make a library of function templates, instead of creating a header file (.h) we should create a "template file" with both the interface and implementation of the function templates (there is no convention on the extension for this type of file other than there be no extension at all or to keep the .h). The inclusion more than once of the same template file with both declarations and definitions in a project doesn't generate linkage errors, since they are compiled on demand and compilers that allow templates should be prepared to not generate duplicate code in these cases.
© The C++ Resources Network, 2000-2003 - All rights reserved |
Previous: 4-4. Polymorphism. |
index |
Next: 5-2. Namespaces. |