Templates
=========
int max( int a, int b)
{
if ( a > b )
return a;
else
return b;
}
double max( double a, double b)
{
if ( a > b )
return a;
else
return b;
}
... and so on ...
but what about the types we haven't written yet?
e.g. max( date, date)
- Preprocessor Macro
#define MAX(a,b) a > b ? a : b
MAX( x, y)*2 --> x > y ? x : y*2
#define MAX(a,b) ((a) > (b) ? (a) : (b))
MAX( ++x, y) --> ++x > y ? ++x : y
it does not always work, since macros are typeless:
void swap( int& x, int& y)
{
int temp = x;
x = y;
y = temp;
}
generic version:
void swap( ..& x, ..& y)
{
.. temp = x;
x = y;
y = temp;
}
Templates are language-integrated version of macros:
template <typename T>
void swap( T& x, T& y)
{
T temp = x;
x = y;
y = temp;
}
template <typename T>
T max( T a, T b)
{
if ( a > b )
return a;
else
return b;
}
template <typename T>
class Vector
{
public:
....
};
The template parameter can be a type, a const expression, or a pointer
to an external object or function.
Template is not a function, template is a pattern to generate a function
The concrete function is the "instance" created by automatic instantiation.
double x, y = 5.1, z = 3.14;
x = max( y, z);
The compiler "deduces" the parameter types, then creates a specific version.
The compiler "instantiates" the "specialization" with concrete types.
Templates must be placed into header files.
The compiler must read their source.
Type exquivalence:
Templates are the same type only if all type parameters are equivalent.
C<char,10> c1;
C<int, 10> c2;
C<int, 25-15> c3;
Instantiation, parameter deduction:
int i = 3, j = 4, k;
double x = 3.14, y = 4.14, z;
const int ci = 6;
k = max( i, j);
z = max( x, y);
k = max( i, ci);
z = max( i, x);
Parameter deduction happens in compilation time.
template <class T, class S>
T max( T a, S b)
{
if ( a > b )
return a;
else
return b;
}
int i = 3;
double x = 3.14;
z = max( i, x);
z == 3.0
template <class R, class T, class S>
R max( T a, S b)
{
if ( a > b )
return a;
else
return b;
}
z = max( i, x);
template <class R, class T, class S>
R max( T a, S b, R)
{
if ( a > b )
return a;
else
return b;
}
z = max( i, x, 0.0);
template <class R, class T, class S>
R max( T a, S b)
{
if ( a > b )
return a;
else
return b;
}
z = max<double>( i, x);
k = max<long, long, int>( i, x);
k = max<int, int, int>( i, j);
Template overloading
template <class T> T max(T,T);
template <class R, class T, class S> R max(T,S);
User specializations
char *s1 = "Hello";
char *s2 = "world";
cout << max( s1, s2);
template <>
template <> const char *max( const char *s1, const char *s2)
{
return strcmp( s1, s2) < 0;
}
Class templates
===============
template <typename T>
class Matrix
{
public:
Matrix( int cols, int rows) : _t ( new T[cols * rows] ) {}
Matrix( const Matrix& rhs);
Matrix& operator=(const Matrix& rhs);
~Matrix();
private:
T* _t;
};
template <typename T>
Matrix<T>::Matrix( const Matrix& rhs) { ... }
^ ^ ^
| | |
class constr name inside Matrix namespace this is Matrix<T>
Possible specializations:
template <>
class Matrix<bool>
{
public:
private:
};
Usage:
#include <matrix.h>
void f()
{
Matrix<int> mi(10,20);
Matrix<double> md(20,40);
Matrix<bool> mb(20,40);
}
Examples:
#ifndef VECTOR_H
#define VECTOR_H
#include <stdexcept>
template <typename T>
class Vector
{
public:
Vector();
Vector(const Vector& rhs);
Vector& operator=(const Vector& rhs);
~Vector();
int size() const;
T& at(int i);
T at(int i) const;
T& operator[](int i);
T operator[](int i) const;
void push_back(T d);
void pop_back();
private:
int _size;
int _capacity;
T* _ptr;
void copy(const Vector& rhs);
void release();
void grow();
};
template <typename T>
Vector<T>::Vector()
{
_capacity = 4;
_size = 0;
_ptr = new T[_capacity];
}
template <typename T>
Vector<T>::Vector(const Vector& rhs)
{
copy(rhs);
}
template <typename T>
Vector<T>& Vector<T>::operator=(const Vector& rhs)
{
if ( this != &rhs )
{
release();
copy(rhs);
}
return *this;
}
template <typename T>
Vector<T>::~Vector()
{
release();
}
template <typename T>
void Vector<T>::copy(const Vector& rhs)
{
_capacity = rhs._capacity;
_size = rhs._size;
_ptr = new T[_capacity];
for (int i = 0; i < _size; ++i)
_ptr[i] = rhs._ptr[i];
}
template <typename T>
void Vector<T>::release()
{
delete [] _ptr;
}
template <typename T>
void Vector<T>::grow()
{
T *_oldptr = _ptr;
_capacity = 2 * _capacity;
_ptr = new T[_capacity];
for ( int i = 0; i < _size; ++i)
_ptr[i] = _oldptr[i];
delete [] _oldptr;
}
template <typename T>
int Vector<T>::size() const
{
return _size;
}
template <typename T>
T& Vector<T>::at(int i)
{
if ( i >= _size )
throw std::out_of_range("bad index");
return _ptr[i];
}
template <typename T>
T Vector<T>::at(int i) const
{
if ( i >= _size )
throw std::out_of_range("bad index");
return _ptr[i];
}
template <typename T>
T& Vector<T>::operator[](int i)
{
return _ptr[i];
}
template <typename T>
T Vector<T>::operator[](int i) const
{
return _ptr[i];
}
template <typename T>
void Vector<T>::push_back(T d)
{
if ( _size == _capacity )
grow();
_ptr[_size] = d;
++_size;
}
template <typename T>
void Vector<T>::pop_back()
{
if ( 0 == _size )
throw std::out_of_range("vector empty");
--_size;
}
#endif
#include <iostream>
#include <string>
#include "vector.h"
int main()
{
Vector<double> x;
for (int i = 0; i < 10; ++i )
x.push_back(i);
Vector<double> y;
for (int i = 0; i < 15; ++i )
y.push_back(i+20);
std::cout << "x = [ ";
for (int i = 0; i < x.size(); ++i )
std::cout << x.at(i) << " ";
std::cout << "]" << std::endl;
std::cout << "y = [ ";
for (int i = 0; i < y.size(); ++i )
std::cout << y.at(i) << " ";
std::cout << "]" << std::endl;
x = y;
std::cout << "x = y" << std::endl;
std::cout << "x = [ ";
for (int i = 0; i < x.size(); ++i )
std::cout << x[i] << " ";
std::cout << "]" << std::endl;
std::cout << "y = [ ";
for (int i = 0; i < y.size(); ++i )
std::cout << y[i] << " ";
std::cout << "]" << std::endl;
x[0] = 999;
std::cout << "x[0] = 999" << std::endl;
std::cout << "x = [ ";
for (int i = 0; i < x.size(); ++i )
std::cout << x[i] << " ";
std::cout << "]" << std::endl;
std::cout << "y = [ ";
for (int i = 0; i < y.size(); ++i )
std::cout << y[i] << " ";
std::cout << "]" << std::endl;
std::cout << "Vector<std::string>" << std::endl;
Vector<std::string> vs;
for (int i = 0; i < 10; ++i )
{
std::string s = "str";
s += char(i+65);
vs.push_back(s);
}
std::cout << "vs = [ ";
for (int i = 0; i < vs.size(); ++i )
std::cout << vs[i] << " ";
std::cout << "]" << std::endl;
return 0;
}
#ifndef STACK_H
#define STACK_H
#include <iosfwd>
#include <stdexcept>
#include <iostream>
#include "vector.h"
template <typename T>
class Stack;
template <typename T>
void print(std::ostream& os, const Stack<T>& ds);
template <typename T>
class Stack
{
friend void print<>(std::ostream&, const Stack&);
public:
int size() const;
bool empty() const;
T top() const;
T& top();
void push(T d);
void pop();
private:
Vector<T> _vec;
};
template <typename T>
int Stack<T>::size() const
{
return _vec.size();
}
template <typename T>
bool Stack<T>::empty() const
{
return 0 == _vec.size();
}
template <typename T>
void Stack<T>::push(T d)
{
_vec.push_back(d);
}
template <typename T>
void Stack<T>::pop()
{
if ( 0 == _vec.size() )
throw std::out_of_range("stack empty");
_vec.pop_back();
}
template <typename T>
T Stack<T>::top() const
{
if ( 0 == _vec.size() )
throw std::out_of_range("stack empty");
return _vec[_vec.size()-1];
}
template <typename T>
T& Stack<T>::top()
{
if ( 0 == _vec.size() )
throw std::out_of_range("stack empty");
return _vec[_vec.size()-1];
}
template <typename T>
void print(std::ostream& os, const Stack<T>& ds)
{
os << "[ ";
for (int i = 0; i < ds._vec.size(); ++i)
os << ds._vec[i] << " ";
os << "]" << std::endl;
}
#endif
#include <iostream>
#include "stack.h"
int main()
{
Stack<double> x;
for (int i = 0; i < 10; ++i )
x.push(i);
Stack<double> y;
for (int i = 0; i < 15; ++i )
y.push(i+20);
print( std::cout, x);
print( std::cout, y);
x = y;
std::cout << "x = y" << std::endl;
print( std::cout, x);
print( std::cout, y);
x.top() = 999;
std::cout << "x.top() = 999" << std::endl;
print( std::cout, x);
print( std::cout, y);
std::cout << "stress test for memory leaks..." << std::endl;
for(int i = 0; i < 1000; ++i)
{
std::cout << i << " ";
for(int j = 0; j < 1000; ++j)
{
Stack<double> z1, z2;
z1.push(j); z2.push(j);
z1 = z2;
}
}
std::cout << " ... ok" << std::endl;
return 0;
}