#include <iostream.h>
#include "arraytemplate.h"
void main()
{
// Create arrays with the desired number of elements
int n;
cin >> n;
Array<float> x(n);
Array<int> y(n);
// Read the data points
for (int i = 0; i < n; i++) {
cin >> x[i] >> y[i];
}
...
}
A class template declaration consists of the keyword template,
followed by a list of template arguments enclosed in angular
brackets (< >), followed
by a class declaration. These template arguments may be type-arguments
preceded by the keyword class and/or numeric argument declarations.
For example:
template<class T> class Array; // Class template declaration or template<class T, int nL> class vector; // For a vector of length nL.
Let's stick with the first case.
template<class T> class Array; // Class template declaration
template<class T> // T="type",e.g.,int or float
class Array {
public:
Array(int n); // Create array of n elements
Array(); // Create array of 0 elements
Array(const Array<T>&); // Copy array
~Array(); // Destroy array
T& operator[](int i); // Subscripting
int numElts(); // Number of elements
Array<T>& operator=(const Array<T>&); // Array assignment
Array<T>& operator=(T); // Scalar assignment
void setSize(int n); // Change size
private:
int num_elts; // Number of elements
T* ptr_to_data; // Pointer to built-in array of elements
void copy(const Array<T>& a); // Copy in elements of a
};
Notice that the declarations of the constructor and destructor
member functions do not include the parameter T.
<class T>,
but are otherwise analogous to ordinary member function definitions.
template<class T>
Array<T>::Array(int n) {
num_elts = n;
ptr_to_data = new T[n];
}
template<class T>
Array<T>::Array() {
num_elts = 0;
ptr_to_data = 0;
}
template<class T>
Array<T>::Array(const Array<T>& a) {
num_elts = a.num_elts;
ptr_to_data = new T[num_elts];
copy(a); // Copy a's elements
}
template<class T>
void Array<T>::copy(const Array<T>& a) {
// Copy a's elements into the elements of *this
T* p = ptr_to_data + num_elts;
T* q = a.ptr_to_data + num_elts;
while (p > ptr_to_data) *--p = *--q;
}
template<class T>
Array<T>::~Array() {
delete [] ptr_to_data;
}
template<class T>
T& Array<T>::operator[](int i) {
#ifdef CHECKBOUNDS
if(i < 0 || i > num_elts)
error("out of bounds");
#endif
return ptr_to_data[i];
}
template<class T>
int Array<T>::numElts() {
return num_elts;
}
template<class T>
Array<T>& Array<T>::operator=(const Array<T>& rhs) {
if ( ptr_to_data != rhs.ptr_to_data ) {
setSize( rhs.num_elts );
copy(rhs);
}
return *this;
}
template<class T>
void Array<T>::setSize(int n) {
if (n != num_elts) {
delete [] ptr_to_data; // Delete old elements,
num_elts = n; // set new count,
ptr_to_data = new T[n]; // and allocate new elements
}
}
template<class T>
Array<T>& Array<T>::operator=(T rhs) {
T* p = ptr_to_data + num_elts;
while (p > ptr_to_data) *--p = rhs;
return *this;
}
We can see from the main() program how templates are
instanced. Array<float> x(n) is an instance of the template.
Here T has become float.
If you do decide to put the template declarations in a .h
file and the template definitions in a .cc file, then
put #include "Array.cc" at the very bottom of the
Array.h file and in the Array.cc file, remove
#include "Array.h". Don't try to compile Array.cc.
Just compile the rest of the source files, like the file with the
main program. The compiler will do the rest. (This works with
g++, but I don't guarantee it for other compilers.)
Array<float> x(n) is created. Even if an error does appear
when instancing a template class, it does not necessarily mean
the template class has a problem. The problem may be in the
instance itself.
Class objects may be declared const in the same way as intrinsic types. Like an intrinsic object, a user-defined object must be assigned a value when it is created. For example, suppose we have defined a class Student where the constructor just requires the student's name as an argument (Student::Student(char* name)). Then we could say create a constant object in main:
const Student Michael("Michael");
A const object can't be changed
after initialization. The compiler will declare an error if you
try to pass a const object to a function that might try to change
the object. One important use of const is to prevent bugs
by safeguarding objects that you know shouldn't be changed. Consider
the following example which uses our Array template. Recall that
strings are arrays of chars.
char day0[]="Sunday";
Array<char>d(7);
int i;
for(i=0;i<7;i++)
d[i] = day0[i];
const Array<char>Day_zero=d; //const Array can't be changed after
//declaration. Notice that this
//calls the copy constructor.
Array<char>aday = Day_zero; //It's ok to set a nonconst object equal
//to a const one. Calls copy constructor.
aday[0] = Day_zero[0]; //Get compiler warning.
int n = Day_zero.numElts(); //Get compiler warning.
The problem is that Day_zero is declared const, but
the operator[]() and the function numElts() are
not declared const. In particular, these functions take
nonconstant arguments, so the compiler is worried that the
functions might change the const Array.
So we need to change the definitions.
template<class T> // T="type",e.g.,int or float
class Array {
public:
Array(int n); // Create array of n elements
Array(); // Create array of 0 elements
Array(const Array<T>&); // Copy array
~Array(); // Destroy array
T& operator[](int i); // Subscripting
//******************** Note the word "const"*************************
int numElts() const; // Number of elements
const T& operator[](int i) const; // Subscripting
//*******************************************************************
Array<T>& operator=(const Array<T>&); // Array assignment
Array<T>& operator=(T); // Scalar assignment
void setSize(int n); // Change size
private:
int num_elts; // Number of elements
T* ptr_to_data; // Pointer to built-in array of elements
void copy(const Array<T>& a); // Copy in elements of a
};
// Function definitions
template<class T>
int Array<T>::numElts() const{
return num_elts;
}
// New subscript operator, returns const T
template <class T>
const T & Array <T>::operator[](int i) const
{ return ptr_to_data[i]; }
// Old supscript operator, not const
template <class T>
T & Array <T>::operator[] (int i)
{ return ptr_to_data[i]; }
When we put const after a function declaration, as in
int numElts() constwe don't mean that this function can't change. We mean that this is a function that can't change the class object that it belongs to. This is in contrast to a nonconst function which can change the object it belongs to. You can pass a const function nonconstant arguments, especially since these arguments often do not refer to
*this object. If these arguments are not
to be changed, then declare them const:
void fn(const Array& A2);
When const appears in front of a function declaration,
we mean that it returns a const value or reference. For example,
the first const in
const T& operator[](int i) const;means that the subscripted variable returns a reference to a const object. The compiler is smart enough to choose between the const and nonconstant operator function:
const T& operator[](int i) const;
T& operator[](int i);
It chooses according to whether
the current object is constant or not. This is a case of operator overloading.
You can also overload functions with respect
to the constness of their explicit arguments. Thus, the
following two functions are not ambiguous:
void fn(Array& A); //used for non-const objects
void fn(const Array& A); //used for const objects
Another solution to our problem is to write
T operator[](int i) const; //does not return a reference T & operator[](int i); //returns a referenceNotice that
T operator[](int i) const does not return a reference.
Rather it returns the value of the ith element of the array,
so that the ith element can't be changed and is kept const.
T & operator[](int i) returns a reference that can later be changed.
Why bother with const? Because it is a good way to safeguard your program and keep out bugs. Recall that the external function
void fn(Array A);
is safe in that it can't overwrite the Array object because it's
passed a copy of A. But suppose Array A is maintained
in a large relational database so that making a copy requires a
lot of time and effort. In that case it's easier to pass a reference
to Array A, thus enhancing the efficiency of your program.
So you write
void fn(Array& A);
But now there's the danger that fn() will overwrite Array A.
Even if you think that fn() doesn't change Array A,
when debug time comes, you can't exclude the possibility. So you
write
void fn(const Array& A);
So the moral of the story is that you should put in const
wherever you can. If you know that the member function doesn't change
*this, i.e., the object of the class to which it belongs,
add const to the end of the declaration.
(By the way, you only put const at the end of a declaration of a function that's a member of a class. It doesn't make sense to put it at the end of the declaration of an external function.)
#include <iostream.h>
template <class T>
T MAX(T num1, T num2) //template function
{
return (num1 > num2 ? num1 : num2);
}
template <class T>
T SQR(T num) //template function
{
return (num * num);
}
int main()
{
float x, y, z1, z2, z3;
long m;
cin >> x >> y;
z1 = MAX(x,y);
z2 = SQR(x+y);
cout << z1 <<" "<< z2 << endl;
cin >> m;
z3 = MAX(m,x); //won't work, template won't convert
//argument types
return 0;
}
You can also use template functions with user-defined types (classes),
as long as the operators in the template function make sense for the class.
For example if you wanted to use MAX with Student,
the ``less than'' symbol (<) would have to be defined in the
class Student.
In C one can define macros that act like template functions, but macros are more error prone. For example if you wrote:
#define sqr(x) (x*x)
func(float x, float y, float z)
{
z = sqr(x + y); //z=x+y*x+y=x+(y*x)+y due to operator
//precedence
cout << z << endl;
}
In addition, macros don't provide type checking.