CS105: Introduction to Computer Programming: C++

C++: Templates and the Standard Template Library

C++ supports the idea of generic programming using templates. In essence, templates allow you to define classes or functions that operate on an arbitrary type of data, instead of a specific type.

Templates

Here's an example of a templated function. This function operates on a generic type that we've chosen to call T (but you could call it anything):
 template< typename T >
 void swap( T& first, T& second ) {
   T temp = first;
   first = second;
   second = temp;
 }
 ...
 int i = 42, j = -1;
 swap( i, j );

 char c = 'a', d = 'x';
 swap( c, d );

 string s1 = "A man, a plan, a canal...";
 string s2 = "...panama!";
 swap( s1, s2 );

This templated function swaps the first and second arguments. Since the arguments are passed as references, the swap will "stick" after the function finishes. Note that this templated function can be passed arguments of primitive types (like int or char) or arguments that are classes (like string).

When the compiler sees a templated function, it looks through the rest of your code to see with which types the function is used. It then generates a separate version of that function for each type of data that could be passed to it.

Templates can also be used with classes. Remember our Array class from before, which held an array of integers? With templates, our Array class can hold almost any data type:

// an array that will hold a generic type T
template< typename T >
class Array {

  friend ostream& operator<<( ostream& os, const Array& arr ) {
    os << "[ ";
    for( int i = 0; i < arr.length; i++ ) os << arr.data[i] << " ";
    os << "]";
    return os;
  }

public:
  Array( int i = 10 ) {
    data = new T[i];           // use T just like a regular type
    length = i;
  }

  Array( const Array& arr ) {
    data = new T[arr.length];  // again, T is our type
    length = arr.length;
  }

  ~Array() {
    delete[] data;
  }

  Array& operator=( const Array& arr ) {
    if( this != &arr ) {
      if( length != arr.length ) {
	delete[] data;
	data = new T[arr.length];
	length = arr.length;
      }
      for( int i = 0; i < arr.length; i++ ) data[i] = arr.data[i];
    }
    return *this;
  }

  T& operator[]( int idx ) {   // our array-access operator will
    return data[idx];          // return a reference to a T
  }

private:
  T* data;                     // this will hold our array of type T
  int length;
};

...


// make an array of characters
Array<char> arr1(26);
for( int i = 0; i < 26; i++ ) arr1[i] = i+'a';
cout << arr1 << endl;

// make an array of integers
Array<int> arr2(10);
for( int i = 0; i < 10; i++ ) arr2[i] = i*i;
cout << arr2 << endl;

Just like with our templated swap function, the class definition is preceded by the template keyword along with our generic type name (which we again call T). We can then create an instance of our templated class by typing (for example) Array<string> stringArray(10);

The Standard Template Library (or STL)

The STL is a collection of data structures and algorithms that use templates to operate on generic types. For example, the STL defines a vector class that can hold an arbitrary amount of any data type for you -- like our Array class above, but much, much, better! Here is list of some of the data structures that the STL provides for you:

You can use the STL just like you'd use the Array class we defined above:

  #include <vector>
  using std::vector;
  ...
  vector<int> v;
  for( int i = 0; i < 10; i++ ) v.push_back(i);  // add 0-9 to our vector
  ...
  for( int i = 0; i < v.size(); i++ ) {          // use v.size() to get the size
    cout << v[i] << endl;                        // overloaded array-access operator!
  }

The STL also provides a variety of useful algorithms that can operate on STL containers like vectors. Many of the STL algorithms use iterators to read and write from containers. You can think of iterators as pointers to elements in a container. For most containers (like vectors) you can get an iterator to the first element with the begin() function and an iterator to the last element with the end() function.

  #include <vector>
  using std::vector;
  #include <algorithm>

  ...

  vector<string> v;
  string i;
  while( cin >> i ) v.push_back(i);         // read strings from input, put them in a vector

  random_shuffle( v.begin(), v.end() );     // shuffle the strings randomly.  Use the begin() and
                                            // end() functions to get iterators to the beginning 
                                            // and end of the vector, which denotes the range that
                                            // we want to shuffle.

  cout << "v: [ "; 
  for( int i = 0; i < v.size(); i++ ) cout << v.at(i) << " ";
  cout << "]" << endl;

  // display the smallest element.  We have to dereference the iterator that
  // min_element returns to get the actual value that the iterator points to.
  cout << "Max: " << *min_element(v.begin(), v.end()) << endl;

  // sort the vector!
  sort( v.begin(), v.end() );
  cout << "v: [ "; 
  for( int i = 0; i < v.size(); i++ ) cout << v.at(i) << " ";
  cout << "]" << endl;

  // look for a certain string: the word "secret"
  vector<string>::iterator result = find( v.begin(), v.end(), "secret" );
  if( result != v.end() ) cout << "You entered the secret word!" << endl;