• Nem Talált Eredményt

Function templates

In document Mechatronic Systems Programming in C++ (Pldal 143-148)

II. fejezet - Modular programming in C++

2. How to use functions on a more professional level?

2.3. Function templates

In the first case, the compiler finds appropriate the signature of the function VectorSum(unsigned int*, int), in the second case, that of the function VectorSum(double*, int). For arrays of other types (like int and float), the compiler sends an error message, since the automatic conversion of pointers is very restricted in C++.

It should not be forgotten that operator overloading can be used in a similar way than function overloading.

However, operators can only be overloaded in the case of user-defined types (struct, class), so this method will only be treated in the next part of the present book.

From the examples above, it can be clearly seen that the task of creating new function variants with new types and realising the same algorithms will become a word processing task with this method (copying blocks and replacing text). In order to avoid this, C++ compilers can be entrusted with the creation of redefined function variants with the usage of function templates.

2.3. Function templates

In the previous parts of this chapter, we have provided an overview of functions which make program codes safer and easier to maintain. Although functions make possible efficient and flexible code development, they have some disadvantages since all parameters have to be assigned a type. Overloading, which was presented in the previous section, helps us make functions independent of types but only to a certain extent. However, function overloading can treat only a finite number of types and also, it is not so efficient to update or to correct repeated codes in the overloaded functions.

So there should be a possibility to create a function only once and to use it as many times as possible with as many types as possible. Nowadays, most programming languages offer the method called generic programming for solving this problem.

Generic programming is a general programming model. This technique means developing a code that is independent of types. So source codes use so-called generic or parameterized types. This principle increases a lot the extent to which a code can be reused since containers and algorithms independent of types can be created with this method. C++ offers templates in order that generic programming could be realised at compilation time.

However, there are languages and systems that realise generic programming at run-time (for example Java and С#).

2.3.1. Creating and using function templates

A template declaration starts with the keyword template, followed by the parameters of the template enclosed within the signs < and >. In most cases, these parameters are generic type names but they can contain variables as well. Generic type names should be preceded by the keyword class or typename.

When we create a function template, we should start from a working function in which the certain types should be replaced by generic types (TYPE). Afterwards, the compiler should be told which types should be replaced in the function template (template<class TYPE>). The first step to take in our function named Absolute():

inline TYPE Absolute(TYPE x) { return x < 0 ? -x : x;

}

Then the final version should be provided in two ways:

template <class TYPE>

inline TYPE Absolute(TYPE x) { return x < 0 ? -x : x;

}

template <typename TYPE>

inline TYPE Absolute(TYPE x) {

return x < 0 ? -x : x;

}

From the function template that is now done, the compiler will create the needed function variant when the function is called. It should be noted that the template named Absolute() can only be used with numeric types among which the operations 'lower than' and 'changing the sign' can be interpreted.

When the function is called in a traditional way, the compiler determines, based on the type of the argument, which version of the function Absolute() it will create and compile.

cout << Absolute(123)<< endl; // TYPE = int cout << Absolute(123.45)<< endl; // TYPE = double

Programmers themselves can carry out a function variant using the type-cast manually in the call:

cout << Absolute((float)123.45)<< endl; // TYPE = float cout << Absolute((int)123.45)<< endl; // TYPE = int

The same result is achieved if the type name is provided within the signs < and > after the name of the function:

cout << Absolute<float>(123.45)<< endl; // TYPE = float cout << Absolute<int>(123.45)<< endl; // TYPE = int

In a function template, more types can be replaced, as it can be seen in the following example:

template <typename T>

inline T Maximum(T a, T b) { return (a>b ? a : b);

}

int main() {

int a = 12, b=23;

float c = 7.29, d = 10.2;

cout<<Maximum(a, b); // Maximum(int, int) cout<<Maximum(c, d); // Maximum(float, float)

↯ cout<<Maximum(a, c); // there is no Maximum(int, float) cout<<Maximum<int>(a,c);

// it is the function Maximum(int, int) that is called with type conversion }

In order to use different types, more generic types should be provided in the function template header:

template <typename T1, typename T2>

inline T1 Maximum(T1 a, T2 b) { return (a>b ? a : b);

}

int main() {

cout<<Maximum(5,4); // Maximum(int, int) cout<<Maximum(5.6,4); // Maximum(double, int) cout<<Maximum('A',66L); // Maximum(char, long) cout<<Maximum<float, double>(5.6,4);

// Maximum(float, double) cout<<Maximum<int, char>('A',66L);

// Maximum(int, char) }

2.3.2. Function template instantiation

When a C++ compiler first encounters a function template call, it creates an instance of the function with the given type(s). Each function instance is a specialised version of the function template with the given types. In the compiled program code, exactly one function instance belongs to each used type. The calls above were examples of the so-called implicit instantiation.

A function template can be instantiated in an explicit way as well if concrete types are provided in the template line containing the header of the function:

template inline int Absolute<int>(int);

template inline float Absolute(float);

template inline double Maximum(double, long);

template inline char Maximum<char, float>(char, float);

It should be noted that only one explicit instantiation can figure in the source code for (a) given type(s).

Since a C++ compiler processes function templates at compilation time, templates have to be available in the form of a source code in order that function variants could be created. It is recommended to place function templates in a header file to make sure that the header file can only be included once in C++ modules.

2.3.3. Function template specialization

There are cases when it is not precisely the algorithm defined in the generic function template that has to be used for some types or type groups. In this case, the function template itself has to be overloaded (specialized) – explicit specialization.

The function template Swap() of the following example code exchanges the values of the passed arguments. In the case of pointers, the specialised version does not exchange pointers but the referenced values. The second specialised version can be used to swap strictly C-styled strings.

#include <iostream>

#include <cstring>

using namespace std;

// The basic template

template< typename T > void Swap(T& a, T& b) { T c(a);

template<typename T> void Swap(T* a, T* b) { T c(*a);

*a = *b;

*b = c;

}

// Template specialised for C strings template<> void Swap(char *a, char *b) { char buffer[123];

delete px;

delete py;

}

From among the different specialised versions, compilers first choose always the most specialised (concrete) template to compile the function call.

2.3.4. Some further function template examples

In order to sum up function templates, let's see some more complex examples. Let's start with making a generic version of the function VectorSum() of 2.2. szakasz - Overloading (redefining) function names. The function template can determine the sum of the elements of any one-dimensional numeric array:

#include <iostream>

using namespace std;

template<class TYPE>

TYPE VectorSum(TYPE a[], int n) { TYPE sum = 0;

const int ni=sizeof(ai) / sizeof(ai[0]);

cout << "\nThe sum of the elements of the int array: "

<<VectorSum(ai,ni);

double ad[]={1.2,2.3,3.4,4.5,5.6};

const int nd=sizeof(ad) / sizeof(ad[0]);

cout << "\nThe sum of the elements of the double array: "

<<VectorSum(ad,nd);

float af[]={3, 2, 4, 5};

const int nf=sizeof(af) / sizeof(af[0]);

cout << "\nThe sum of the elements of the float array: "

<<VectorSum(af,nf);

long al[]={1223, 19800729, 2004102};

const int nl=sizeof(al) / sizeof(al[0]);

cout << "\nThe sum of the elements of the long array: "

<<VectorSum(al,nl);

}

The function template Sort() sorts the elements of any one-dimensional array of type Typ and of size Size in an ascending order. The function moves the elements within the array and the result is stored in the same array as well. Since this declaration contains an integer parameter (Size) besides the type parameter (Typ), the function has to be called in the extended version:

Sort<int, size>(array);

The example code also calls the Swap() template from the the template Sort():

#include <iostream>

using namespace std;

template< typename T > void Swap(T& a, T& b) { T c(a);

a = b;

b = c;

}

template<class Typ, int Size> void Sort(Typ vector[]) { int j;

for(int i = 1; i < Size; i++){

j = i; these functions are called. From the specialised variants, it is the generic template that is activated which prints out the elements of the complete function call with the help of the template PrintOut(). The name of the types is returned by the member name () of the object returned by the typeid operator. The function template Character() is used to print out characters enclosed in apostrophes. This little example code presents some ways function templates can be used.

template <typename T> T Character(T a) { return a;

template <typename TA, typename TB>

void PrintOut(TA a, TB b){

cout << "Function<" << typeid(TA).name() << ","

<< typeid(TB).name();

cout << ">(" << Character(a) << ", " << Character(b) << ")\n";

}

template <typename TA, typename TB>

void Function(TA a = 0, TB b = 0){

Function<int, char>();

Function('A', 'X');

Function();

}

The results:

Function<int,char>(1, 'c') Function<double,double>(1.5, 0) Function<int,double>(0, 0) Function<int,char>(0, 0) Function<char,char>('A', 'X') Function<int,int>(12, 23)

In document Mechatronic Systems Programming in C++ (Pldal 143-148)