Introduction to Programming and C++

Contents Previous Topic Next Topic

In Java and C#, because every class is derived (a subclass of) the type Type, it is therefore possible to generate a container which stores all possible classes, for example

	Type [] array = new Type[10];

generates an array of ten references to Type.

Because there is no ultimate class in C++, this would prevent us from creating such an array. Similarly, we could not create a generic linked list class or a generic queue: we would either be forced to using pointers and casting (a rather ugly solution leading to no end of headaches and software engineering issues). Fortunately, templates save us:

Templates and Functions

A template can be thought of as a compile-time function: a function takes a parameter which must be a of the specific type. For example, the function

	int sqr( int n ) {
		return n*n;
	}

takes one parameter which must be of type int. The compiler checks that the type of argument to this function is of the specified type. The parameter n then takes on the value of the argument (at least, initially -- parameters may be assigned to).

A template is similar to an argument, only (as far as this class is concerned) it takes a type:

	template <typename T>
	T sqr( T t ) {
		T loc_var = t*t;
		return loc_var;
	}

Thus, this defines a function which takes an argument of type T and returns an argument of type T. I use t instead of n to emphasize that this may not be an integer.

Like a function, we can replace the template variable T with an actual type: thus, for example:

sqr<int>( ... )
This defines a function which takes an int as an argument and returns an int.
sqr<double>( ... )
This defines a function which takes an double as an argument and returns an double.

As this suggests, the T in the first case is substituted with int, and thus, the function takes an int as a parameter, the local variable loc_var is of type int, and the function returns an int. It is the same in the second case, however, it substitutes double.

This need not be restricted to built-in data types. For example, available on my web site is a class template <typename T> class complex; which allows you to define complex numbers where both components are either float or double. Thus, the function sqr<complex<double> >( ... ) takes a complex<double> as an argument, loc_var has the same type, and a complex<double> is returned.

You can try to compile the following code:

#include <iostream>
using namespace std;

template <typename T>
T sqr( T t ) {
	T loc_var = t*t;
	return loc_var;
}

int main() {
	for ( int i = 0; i < 5; ++i ) {
		cout << "The square of " << i << " is "
		     << sqr<int>( i ) << endl;
	}

	for ( double x = 3.141592653589793; x < 4; x += 0.1 ) {
		cout << "The square of " << x << " is "
		     << sqr<double>( x ) << endl;
	}

	return 0;
}

Templates and Classes

Alternatively, you can create a class with a template. For example, the next piece of code defines a box which stores the templated type:

#include <iostream>
using namespace std;

template <typename T>
class Box {
	private:
		T value;

	public:
		Box( T const &v = T() ):value( v ) {
			// empty
		}

		T get() const {
			return value;
		}

		void set( T v ) {
			value = v;
		}
};

int main() {
	Box<int> bx0( 30 );

	cout << bx0.get() << endl;
	bx0.set( 42 );
	cout << bx0.get() << endl;

	Box<double> dx1( 3.14 );

	cout << dx1.get() << endl;
	dx1.set( 2.71 );
	cout << dx1.get() << endl;

	return 0;
}

In this example, we have defined the member functions inside the class. Inside int main(), when the variable bx0 is declared, it instantiates an instance of the class Box where all of the symbols T are replaced by int. Similarly, the declaration of the variable dx1 creates an instance of the class Box where all the symbols T are replaced by double.

Thus, this class defines a container which holds exactly one instance of any type we choose.

In the above example, the member functions are defined inside the class member declaration. In reality (and for your projects), it is much more common for the member functions to be defined outside the class. Thus, for each member function definition, we must restate the modifier template<typename T>. For example:

#include <iostream>
using namespace std;

template <typename T>
class Box {
	private:
		T value;

	public:
		Box( const T & = T() );

		T get() const;

		void set( T );
};

template<typename T>
Box<T>::Box( T const &v ):
value( v ) {
	// empty
}

template<typename T>
T Box<T>::get() const {
	return value;
}

template<typename T>
void Box<T>::set( T v ) {
	value = v;
}

Notice that each time I refer to Box outside of the class declaration, I refer to Box<T>. In some cases, you can leave off the <T>, however, there is little harm in leaving them.

Now you can have a container (a box) which stores anything. Unfortunately, this is not very useful, and therefore, instead of storing just one object, we could store an array of objects. In the following example, the user specifies how many entries the array should have and then accesses them.

#include <iostream>
using namespace std;

template <typename T>
class BoxArray {
	private:
		T * array;
		int count;

	public:
		BoxArray( int );
		~BoxArray();

		T get( int ) const;

		void set( int, T );
};

template<typename T>
BoxArray<T>::BoxArray<T>( int c ):count( c ), array( new T[count] ) {
	// count = n;
	// array = new T[count];
}

template<typename T>
BoxArray<T>::~BoxArray<T>() {
	delete [] array;
}

template<typename T>
T BoxArray<T>::get( int n ) const {
	return array[n];
}

template<typename T>
void BoxArray<T>::set( int n, T v ) {
	array[n] = v;
}

int main() {
	BoxArray<char>box( 26 );   // an array of 26 characters
	
	for ( int n = 0; n < 26; ++n ) {
		box.set( n, 'a' + n );    // 'a' + 0 == 'a';
		                          // 'a' + 1 == 'b'; etc.
	}
	
	cout << box.get( 0 );

	for ( int i = 0; i < 26; ++i ) {
		cout << "-" << box.get( i );
	}

	return 0;
}

Contents Previous Topic Top Next Topic