9 CPP Template
9 CPP Template
| HOME
Programming
1. Introduction
We are familiar in passing value/variable into function. Instead of passing a variable, we pass a type (such as int, double, and
Point) into template. Passing type is known as generic programming, as we can program in generic type and invoke the code using
a specific type.
The goal of generic programming is to write code that is independent of the data types. In C language, all codes are tied to a specific
data type. For container data structures (such as array and structure), you need to specify the type of the elements. For algorithms
(such as searching or sorting), the code works only for a specific type, you need to rewrite the code for another type. Can we write a
single sorting routine that works on all types (or most of the types) by specifying the type during invocation? Can we have a general
container that can work on all types?
Template lets you program on generic type, instead of on a specific type. Template supports so-called parameterized type - i.e., you
can use type as argument in building a class or a function (in class template or function template). Template is extremely useful if a
particular algorithm is to be applied to a variety of types, e.g., a container class which contains elements, possibly of various types.
C++'s Standard Template Library (STL) provides template implementation of many container classes, such as vector, which can be
used to hold elements of all types. STL also provides generic representation of algorithm (such as searching and sorting), which
works on the generic container.
C++ provides a vector template class, as part of Standard Template Library (STL). It is defined in header <vector>, belonging to
the namespace std. (In computing, a vector refers to an array-like structure that holds a set of direct-access elements of the same
kinds, instead of mathematical n-component vector.) vector is the most commonly used STL class, which provides an alternative to
the error-phone array, supports dynamic memory allocation and many operations (such as comparison and assignment).
vector is a template class, which can be instantiated with a type, in the format: vector<int>, vector<double>,
vector<string>. The same template class can be used to handle many types, instead of repeatably writing codes for each of the
type.
Program Notes:
You can instantiate a vector of int via declaration vector<int> v1(n), where n specifies the initial number of elements.
You can use v1.size() to get the current size (number of elements) of the vector.
You can directly access element via v1[i] or v1.at(i). The [] operator does not perform index-bound check; whereas at()
does. Try issuing an out-of-bound index via [] and at() and observe the result.
You can assign a vector into another vector via the assignment operator (=). The assignment is carried out memberwise.
You can compare two vectors memberwise via the comparison operators (==, !=).
You can append/remove element from the end via push_back() and pop_back(). The size of vector will be adjusted and
memory allocated automatically.
You can also create vector of other types, such as vector<string>, vector<double>, etc. The bigger advantage of template
class is the same algorithm and operation can be applied to different types - you do not need to write separate codes for different
types.
3. Function Template
A function template is a generic function that is defined on a generic type for which a specific type can be substituted. Compiler will
generate a function for each specific type used. Because types are used in the function parameters, they are also called
parameterized types.
Similar to function's parameter-list, which passes variables (e.g., int number), the template's parameter list passes types.
Example 1
1 /* Test Function Template (FuncationTemplate.cpp) */
2 #include <iostream>
3 using namespace std;
4
5 template <typename T>
6 void mySwap(T &a, T &b);
7 // Swap two variables of generic type passed-by-reference
8 // There is a version of swap() in <iostream>
9
10 int main() {
11 int i1 = 1, i2 = 2;
12 mySwap(i1, i2); // Compiler generates mySwap(int &, int &)
13 cout << "i1 is " << i1 << ", i2 is " << i2 << endl;
14
15 char c1 = 'a', c2 = 'b';
16 mySwap(c1, c2); // Compiler generates mySwap(char &, char &)
17 cout << "c1 is " << c1 << ", c2 is " << c2 << endl;
18
19 double d1 = 1.1, d2 = 2.2;
20 mySwap(d1, d2); // Compiler generates mySwap(double &, double &)
21 cout << "d1 is " << d1 << ", d2 is " << d2 << endl;
22
23 // mySwap(i1, d1);
24 // error: no matching function for call to 'mySwap(int&, double&)'
25 // note: candidate is:
26 // note: template<class T> void mySwap(T&, T&)
27 }
28
29 template <typename T>
30 void mySwap(T &a, T &b) {
31 T temp;
32 temp = a;
33 a = b;
34 b = temp;
35 }
Take note that the C++ compiler generate a version of the code for each type used in the program, in a process known as
instantiation of template. For example, with type of int, the following code will be generated by the compiler:
There is no improvement in term of execution speed or memory usage. But there is a big improvement in programmer's productivity,
ease-of-use and ease-of-maintenance.
Also, the above code can handle the fundamental types (such as int, double), but not some types such as array. It, however, works
for objects and structs, thanks to the memberwise assignment.
You could use <typename T> or <class T> in the template parameter list. The keywords typename and class serve the same
purpose.
Example 2
1 /* Test function Template (TestFunctionTemplate.cpp) */
2 #include <iostream>
3 using namespace std;
4
5 template<typename T>
6 T abs(T value) {
7 T result; // result's type is also T
8 result = (value >= 0) ? value : -value;
9 return result;
10 }
11
12 int main() {
13 int i = -5;
14 cout << abs(i) << endl;
15
16 double d = -55.5;
17 cout << abs(d) << endl;
18
19 float f = -555.5f;
20 cout << abs(f) << endl;
21 }
The compiler creates three versions of the abs function based on the function template, for types of int, double, and float.
Take note that if there is any non-template definition that matches the function call. The non-template version will take precedence
over explicit specialization, then template.
4. Class Template
The syntax for defining a class template is as follow, where T is a placeholder for a type, to be provided by the user.
The keywords class and typename (newer and more appropriate) are synonymous in the definition of template.
For Example,
1 /*
2 * Test Class Template (TestClassTemplate.cpp)
3 */
4 #include <iostream>
5 using namespace std;
6
7 template <typename T>
8 class Number {
9 private:
10 T value;
11 public:
12 Number(T value) { this->value = value; };
13 T getValue() { return value; }
14 void setValue(T value) { this->value = value; };
15 };
16
17 int main() {
18 Number<int> i(55);
19 cout << i.getValue() << endl;
20
21 Number<double> d(55.66);
22 cout << d.getValue() << endl;
23
24 Number<char> c('a');
25 cout << c.getValue() << endl;
26
27 Number<string> s("Hello");
28 cout << s.getValue() << endl;
29 }
A particular realization of template is called an instantiation or specialization. The C++ compiler generate a class for each of the
parameterized type used in the program.
Default Type
You can also provide default type in template. For example,
Specialization
// General Template
template <typename T>
class Complex { ...... }
MyComplex.h
1 /*
2 * The MyComplex template class header (MyComplex.h)
3 * All template codes are kept in the header, to be included in program
4 * (Follow, modified and simplified from GNU GCC complex template class.)
5 */
6 #ifndef MY_COMPLEX_H
7 #define MY_COMPLEX_H
8
9 #include <iostream>
10
11 // Forward declaration
12 template <typename T> class MyComplex;
13
14 template <typename T>
15 std::ostream & operator<< (std::ostream & out, const MyComplex<T> & c);
16 template <typename T>
17 std::istream & operator>> (std::istream & in, MyComplex<T> & c);
18
19 // MyComplex template class declaration
20 template <typename T>
21 class MyComplex {
22 private:
23 T real, imag;
24
25 public:
26 // Constructor
27 explicit MyComplex<T> (T real = 0, T imag = 0)
28 : real(real), imag(imag) { }
29
30 // Overload += operator for c1 += c2
31 MyComplex<T> & operator+= (const MyComplex<T> & rhs) {
32 real += rhs.real;
33 imag += rhs.imag;
34 return *this;
35 }
36
37 // Overload += operator for c1 += value
38 MyComplex<T> & operator+= (T value) {
39 real += value;
40 return *this;
41 }
42
43 // Overload comparison == operator for c1 == c2
44 bool operator== (const MyComplex<T> & rhs) const {
45 return (real == rhs.real && imag == rhs.imag);
46 }
47
48 // Overload comparison != operator for c1 != c2
49 bool operator!= (const MyComplex<T> & rhs) const {
50 return !(*this == rhs);
51 }
52
53 // Overload prefix increment operator ++c
54 // (Separate implementation for illustration)
55 MyComplex<T> & operator++ ();
56
57 // Overload postfix increment operator c++
58 const MyComplex<T> operator++ (int dummy);
59
60 /* friends */
61
62 // (Separate implementation for illustration)
63 friend std::ostream & operator<< <>(std::ostream & out, const MyComplex<T> & c); // out << c
64 friend std::istream & operator>> <>(std::istream & in, MyComplex<T> & c); // in >> c
65
66 // Overloading + operator for c1 + c2
67 // (inline implementation for illustration)
68 friend const MyComplex<T> operator+ (const MyComplex<T> & lhs, const MyComplex<T> & rhs) {
69 MyComplex<T> result(lhs);
70 result += rhs; // uses overload +=
71 return result;
72 }
73
74 // Overloading + operator for c + double
75 friend const MyComplex<T> operator+ (const MyComplex<T> & lhs, T value) {
76 MyComplex<T> result(lhs);
77 result += value; // uses overload +=
78 return result;
79 }
80
81 // Overloading + operator for double + c
82 friend const MyComplex<T> operator+ (T value, const MyComplex<T> & rhs) {
83 return rhs + value; // swap and use above function
84 }
85 };
86
87 // Overload prefix increment operator ++c
88 template <typename T>
89 MyComplex<T> & MyComplex<T>::operator++ () {
90 ++real; // increment real part only
91 return *this;
92 }
93
94 // Overload postfix increment operator c++
95 template <typename T>
96 const MyComplex<T> MyComplex<T>::operator++ (int dummy) {
97 MyComplex<T> saved(*this);
98 ++real; // increment real part only
99 return saved;
100 }
101
102 /* Definition of friend functions */
103
104 // Overload stream insertion operator out << c (friend)
105 template <typename T>
106 std::ostream & operator<< (std::ostream & out, const MyComplex<T> & c) {
107 out << '(' << c.real << ',' << c.imag << ')';
108 return out;
109 }
110
111 // Overload stream extraction operator in >> c (friend)
112 template <typename T>
113 std::istream & operator>> (std::istream & in, MyComplex<T> & c) {
114 T inReal, inImag;
115 char inChar;
116 bool validInput = false;
117 // Input shall be in the format "(real,imag)"
118 in >> inChar;
119 if (inChar == '(') {
120 in >> inReal >> inChar;
121 if (inChar == ',') {
122 in >> inImag >> inChar;
123 if (inChar == ')') {
124 c = MyComplex<T>(inReal, inImag);
125 validInput = true;
126 }
127 }
128 }
129 if (!validInput) in.setstate(std::ios_base::failbit);
130 return in;
131 }
132
133 #endif
Program Notes:
[TODO]
TestMyComplex.cpp
1 /* Test Driver for MyComplex template class (TestMyComplex.cpp) */
2 #include <iostream>
3 #include <iomanip>
4 #include "MyComplex.h"
5
6 int main() {
7 std::cout << std::fixed << std::setprecision(2);
8
9 MyComplex<double> c1(3.1, 4.2);
10 std::cout << c1 << std::endl; // (3.10,4.20)
11 MyComplex<double> c2(3.1);
12 std::cout << c2 << std::endl; // (3.10,0.00)
13
14 MyComplex<double> c3 = c1 + c2;
15 std::cout << c3 << std::endl; // (6.20,4.20)
16 c3 = c1 + 2.1;
17 std::cout << c3 << std::endl; // (5.20,4.20)
18 c3 = 2.2 + c1;
19 std::cout << c3 << std::endl; // (5.30,4.20)
20
21 c3 += c1;
22 std::cout << c3 << std::endl; // (8.40,8.40)
23 c3 += 2.3;
24 std::cout << c3 << std::endl; // (10.70,8.40)
25
26 std::cout << ++c3 << std::endl; // (11.70,8.40)
27 std::cout << c3++ << std::endl; // (11.70,8.40)
28 std::cout << c3 << std::endl; // (12.70,8.40)
29
30 // c1+c2 = c3; // error: c1+c2 returns a const
31 // c1++++; // error: c1++ returns a const
32
33 // MyComplex<int> c4 = 5; // error: implicit conversion disabled
34 MyComplex<int> c4 = (MyComplex<int>)5; // explicit type casting allowed
35 std::cout << c4 << std::endl; // (5,0)
36
37 MyComplex<int> c5;
38 std::cout << "Enter a complex number in (real,imag): ";
39 std::cin >> c5;
40 if (std::cin.good()) {
41 std::cout << c5 << std::endl;
42 } else {
43 std::cerr << "Invalid input" << std::endl;
44 }
45 return 0;
46 }
Program Notes:
[TODO]
Feedback, comments, corrections, and errata can be sent to Chua Hock-Chuan ([email protected]) | HOME