Cpp Chapter 8: Adventures in Functions Part5
8.5.2 Overloaded Templates
) As with ordinary overloading, overloaded templates need distinct function signatures:
template <typename T> void Swap(T &a, T &b); template <typename T> void Swap(T *a, T *b, int n);
also note from this example that not all template arguments have to be template parameter types. Here's an example:
1 // twotemps.cpp -- using overloaded template functions 2 #include <iostream> 3 template <typename T> 4 void Swap(T &a, T &b); 5 6 template <typename T> 7 void Swap(T *a, T *b, int n); 8 9 void Show(int a[]); 10 const int Lim = 8; 11 int main() 12 { 13 using namespace std; 14 int i = 10, j = 20; 15 cout <<"i, j = " << i << ", " << j << ".\n"; 16 cout << "Using complier-generated int swapper:\n"; 17 Swap(i,j); 18 cout << "Now i, j = " << i << ", " << j << ".\n"; 19 20 int d1[Lim] = {0,7,0,4,1,7,7,6}; 21 int d2[Lim] = {0,7,2,0,1,9,6,9}; 22 cout << "Original arrays:\n"; 23 Show(d1); 24 Show(d2); 25 Swap(d1, d2, Lim); 26 cout << "Swapped arrays:\n"; 27 Show(d1); 28 Show(d2); 29 return 0; 30 } 31 32 template <typename T> 33 void Swap(T &a, T &b) 34 { 35 T temp; 36 temp = a; 37 a = b; 38 b = temp; 39 } 40 41 template <typename T> 42 void Swap(T a[], T b[], int n) 43 { 44 T temp; 45 for (int i = 0; i < n; i++) 46 { 47 temp = a[i]; 48 a[i] = b[i]; 49 b[i] = temp; 50 } 51 } 52 53 void Show(int a[]) 54 { 55 using namespace std; 56 cout << a[0] << a[1] << "/" << a[2] << a[3] << "/"; 57 for (int i = 4; i < Lim; i++) 58 cout << a[i]; 59 cout << endl; 60 }
8.5.3 Template Limitations
) When defined a template function with typename T, certain operators might not make sense with the given type T:
template <typename T> void process(T *a, T *b) { if (a < b) ... }
Here the operator "<" compares the address of two pointers, and this is mostly not wanted. In later chapters, you will learn to have overloaded operators to tackle this sort of problem.
8.5.4 Explicit Specializations
) Explicit specialization is to provide a function to the template with specific parameter type. If the complier finds a specialized definition that exactly matches a function call, it uses the definition without searching for templates.
) How prototypes differ from regular function to template function to explicit specialization:
// trying to swap a job structure void Swap(job &, job &); template <typename T> void Swap(T &, T &); template <> void Swap<job>(job &, job &);
Precendence: non-template version > explicit specializations > template versions. Explicit specialization prototypes could also omit <job>:
template <> void Swap(job &, job &);
Here's an example:
1 // twoswap.cpp -- specialization overrides a template 2 #include <iostream> 3 template <typename T> 4 void Swap(T &a, T &b); 5 6 struct job 7 { 8 char name[40]; 9 double salary; 10 int floor; 11 }; 12 13 template <> void Swap<job>(job &j1, job &j2); 14 void Show(job &j); 15 16 int main() 17 { 18 using namespace std; 19 cout.precision(2); 20 cout.setf(ios_base::fixed, ios_base::floatfield); 21 int i = 10, j = 20; 22 cout << "i, j = " << i << ", " << j << ".\n"; 23 cout << "Using compiler-generated int swapper:\n"; 24 Swap(i,j); 25 cout << "Now i, j = " << i << ", " << j << ".\n"; 26 27 job sue = {"Susan Yaffee", 73000.60, 7}; 28 job sidney = {"Sidney Taffee", 78060.72, 9}; 29 cout << "Before job swapping:\n"; 30 Show(sue); 31 Show(sidney); 32 Swap(sue,sidney); 33 cout << "After job swapping:\n"; 34 Show(sue); 35 Show(sidney); 36 return 0; 37 } 38 39 template <typename T> 40 void Swap(T &a, T &b) 41 { 42 T temp; 43 temp = a; 44 a = b; 45 b = temp; 46 } 47 48 template <> void Swap<job>(job &j1, job &j2) 49 { 50 double t1; 51 int t2; 52 t1 = j1.salary; 53 j1.salary = j2.salary; 54 j2.salary = t1; 55 t2 = j1.floor; 56 j1.floor = j2.floor; 57 j2.floor = t2; 58 } 59 60 void Show(job &j) 61 { 62 using namespace std; 63 cout << j.name << ": $" << j.salary << " on floor " << j.floor << endl; 64 }
8.5.5 Instantiations and Specializations
) There are three ways of using templates: implicit instantiation, explicit instantiation and explicit specialization. These three concepts are totally termed specialization. They all represent a function definition that use specific types rather than a generic description.
1. implicit instantiation: According to your input arguments, the complier deduces what type you are using and generate a function definition specific for that type.
template <typename T> void Swap(T &a, T &b); int a,b; a = 2; b = 3; Swap(a,b); // the compiler generates function definition for Swap() using int
2. explicit instantiation: You explicitly tell the complier to generate a function definition using a given type:
template <typename T> void Swap(T &a, T &b); template void Swap<int>(int, int); // explicit instantiation int a,b; a = 2; b = 3; Swap(a,b); // now uses the generated Swap() definition using int by explicit instantiation
3. explicit specialization: You design a brand new function for a new type using the old name(distinct from ex. instantiation that the function definition here is provided by yourself and for specific type, unlike in explicit instantiation the function definition is generated according to the template using specific type):
template <typename T> void Swap(T &a, T &b); struct job { ... }; template <> void Swap<job>(job &, job &); // explicit specialization job a, b; Swap(a,b); // using the function definition for struct job by explicit specialization
Next comes an example illustrating this features:
1 template <class T> 2 void Swap(T &, T &); 3 4 template <> void Swap<job>(job &, job &); // ex. specialization 5 int main() 6 { 7 template void Swap<char>(char &, char &); 8 short a, b; 9 Swap(a,b); // im. instantiation and using it 10 job n, m; 11 Swap(n,m); // using ex. specialization 12 char g, h; 13 Swap(g,h); // using ex. instantiation 14 }
8.5.6 Which Function Version Does the Compiler Pick?
) Overload resolution: a process for choosing the function to call
Phase 1: Assemble a list of candidate functions, which are functions and templates that have the same names as the called functions
Phase 2: From the candidate functions, assemble a list of viable functions. These are functions with the correct number of arguments and for which there is an implicit conversion sequence, which includes the case of exact match for each type of actual argument to the type of the corresponding formal argument.
Phase 3: Determine whether there is a best viable function. If so, you use that function, otherwise the function call is an error.
Precedence: 1)Exact Match 2)Conversion by promotion(char/short to int & float to double) 3)Conversion by standard conversion(int to char & long to double) 4) User-defined conversion
) There are trivial conversions that are allowed for exact match: involving * & const volatile means exact match
) Exception is that pointers and references to non-const data are preferentially matched to non-const pointer and reference.\
) A non-template function is considered better than a template function.
) Faced with multiple template functions, the more specialized one is the better(which means functions that take fewer conversions are better.
) Facing different template functions both could be matched after the same steps of conversion, a rule called the partial ordering rules finds out the more specialized function:
1 // tempover.cpp -- template overloading 2 #include <iostream> 3 4 template <typename T> 5 void ShowArray(T arr[], int n); 6 7 template <typename T> 8 void ShowArray(T * arr[], int n); // T * arr[] means an array of pointers 9 10 struct debts 11 { 12 char name[50]; 13 double amount; 14 }; 15 16 int main() 17 { 18 using namespace std; 19 int things[6] = {13, 31, 103, 301, 310, 130}; 20 struct debts mr_E[3] = 21 { 22 {"Ima Wolfe", 2400.0}, 23 {"Ura Foxe", 1300.0}, 24 {"Iby Stout", 1800.0} 25 }; 26 double * pd[3]; 27 28 for (int i = 0; i < 3; i++) 29 { 30 pd[i] = &mr_E[i].amount; 31 } 32 33 cout << "Listing Mr. E's counts of things:\n"; 34 35 ShowArray(things, 6); 36 cout << "Listing Mr. E's debts:\n"; 37 ShowArray(pd, 3); 38 return 0; 39 } 40 41 template <typename T> 42 void ShowArray(T arr[], int n) 43 { 44 using namespace std; 45 cout << "template A\n"; 46 for (int i = 0; i < n; i++) 47 cout << arr[i] << " "; 48 cout << endl; 49 } 50 51 template <typename T> 52 void ShowArray(T * arr[], int n) 53 { 54 using namespace std; 55 cout << "template B\n"; 56 for (int i = 0; i < n; i++) 57 cout << *arr[i] << endl; 58 cout << endl; 59 }
In this example while calling ShowArray(pd, 3) on line 37, both templates match it. But template B make it more specialized that the input might be an array of pointers, which is just the case of pd, so template B in this case is obviously more specialized, hence template B is called in this case.
To sum up the order of function choosing:
1) The overload resolution process looks for a function that's the best match. If just one, the function is chosen.
2) If more than one are tied, but only one is a non-template function, the non-template one is chosen.
3) If more than one candidate are tied and all are template functions, but one template is more specialized than the others, that one is chosen.
4) If there are two or more equally good non-template functions, or if there are two or more equally good template functions, none of which is more specialized than the rest, the function call is ambiguous and raises an error.
8.5.7 Template Function Evolution
) What's that type?
template<class T1, class T2> void ft(T1 x, T2 y) { ... ?type? xpy = x + y; ... }
In this case, the type of xpy is not determined. C++ develops the decltype keyword to tackle this case:
decltype(x+y) xpy; // making xpy the same type as x+y
In this way, decltype(x+y) represents a type. So the given example could be rewrited using decltype:
template<class T1, class T2> void ft(T1 x, T2 y) { ... decltype(x+y) xpy = x + y; ... }
) How about the return type?
template<class T1, class T2> ?type? gt(T1 x, T2 y) { ... return x+y; ... }
the return type problem couldn't be handled by decltype() because x and y are not specified before the position for return type.
Now use trailing return type feature to handle:
template<class T1, class T2> auto gt(T1 x, T2 y) -> decltype(x+y) // trailing return type { ... return x+y; ... }
auto acts as a place holder here and the function return type are finally converted to decltype(x+y). x and y is available here because it is after the function prototype.
Chapter 8 finished now.