CS253

This file defines the header for each page. An optional "icon" image (I use the textbook):

Replace this with info about this class:

CS253: Problem Solving with C++

Spring 2013

Template 1

Links to the various pages for this class:

Wish I could do this: * Schedule

Templates

This presentation was stolen from Chuck Anderson.

    #include <iostream>
    using namespace std;

    int *copyInts(int *src, int num) {
       int *dest = new int[num];
       for (int i=0; i<num; i++)
          dest[i] = src[i];
       return dest;
    }

    double *copyDoubles(double *src, int num) {
       double *dest = new double[num];
       for (int i=0; i<num; i++)
          dest[i] = src[i];
       return dest;
    }

    char *copyLetters(char *src, int num) {
       char *dest = new char[num];
       for (int i=0; i<num; i++)
          dest[i] = src[i];
       return dest;
    } 

    void printInts(int *src, int num) {
       for (int i=0; i<num; i++)
          cout << src[i] << ' ';
       cout << endl;
    }

    void printDoubles(double *src, int num) {
       for (int i=0; i<num; i++)
          cout << src[i] << ' ';
       cout << endl;
    }

    void printLetters(char *src, int num) {
       for (int i=0; i<num; i++)
          cout << src[i] << ' ';
       cout << endl;
    }

    int main() {
       int ints[5] = {1, 2, 3, 4, 5};
       double doubles[4] = {0.1, 0.2, 0.3, 0.4};
       char letters[6] = {'a', 'b', 'c', 'd', 'e', 'f'};

       int *ints2 = copyInts(ints,5);
       double *doubles2 = copyDoubles(doubles,4);
       char *letters2 = copyLetters(letters,6);

       printInts(ints,5);
       printInts(ints2,5);

       printDoubles(doubles,4);
       printDoubles(doubles2,4);

       printLetters(letters,6);
       printLetters(letters2,6);
    }

When run, we see:

    1 2 3 4 5
    1 2 3 4 5
    0.1 0.2 0.3 0.4
    0.1 0.2 0.3 0.4
    a b c d e f
    a b c d e f

Can this code be simplified? Yep. We don’t need printInts, printDoubles and printLetters. All can be named print, because the types of the compiler can disambiguate the calls to print by the types of the arguments.

    #include <iostream>
    using namespace std;

    int *copyInts(int *src, int num) {
       int *dest = new int[num];
       for (int i=0; i<num; i++)
          dest[i] = src[i];
       return dest;
    }

    double *copyDoubles(double *src, int num) {
       double *dest = new double[num];
       for (int i=0; i<num; i++)
          dest[i] = src[i];
       return dest;
    }

    char *copyLetters(char *src, int num) {
       char *dest = new char[num];
       for (int i=0; i<num; i++)
          dest[i] = src[i];
       return dest;
    } 

    void print(int *src, int num) {
       for (int i=0; i<num; i++)
          cout << src[i] << ' ';
       cout << endl;
    }

    void print(double *src, int num) {
       for (int i=0; i<num; i++)
          cout << src[i] << ' ';
       cout << endl;
    }

    void print(char *src, int num) {
       for (int i=0; i<num; i++)
          cout << src[i] << ' ';
       cout << endl;
    }

    int main() {
       int ints[5] = {1, 2, 3, 4, 5};
       double doubles[4] = {0.1, 0.2, 0.3, 0.4};
       char letters[6] = {'a', 'b', 'c', 'd', 'e', 'f'};

       int *ints2 = copyInts(ints,5);
       double *doubles2 = copyDoubles(doubles,4);
       char *letters2 = copyLetters(letters,6);

       print(ints,5);
       print(ints2,5);

       print(doubles,4);
       print(doubles2,4);

       print(letters,6);
       print(letters2,6);
    }

And we still get:

    1 2 3 4 5
    1 2 3 4 5
    0.1 0.2 0.3 0.4
    0.1 0.2 0.3 0.4
    a b c d e f
    a b c d e f

Can we do the same for the copy functions?

    #include <iostream>
    using namespace std;

    int *copy(int *src, int num) {
       int *dest = new int[num];
       for (int i=0; i<num; i++)
          dest[i] = src[i];
       return dest;
    } 

    double *copy(double *src, int num) {
       double *dest = new double[num];
       for (int i=0; i<num; i++)
          dest[i] = src[i];
       return dest;
    }

    char *copy(char *src, int num) {
       char *dest = new char[num];
       for (int i=0; i<num; i++)
          dest[i] = src[i];
       return dest;
    } 

    void print(int *src, int num) {
       for (int i=0; i<num; i++)
          cout << src[i] << ' ';
       cout << endl;
    }

    void print(double *src, int num) {
       for (int i=0; i<num; i++)
          cout << src[i] << ' ';
       cout << endl;
    }

    void print(char *src, int num) {
       for (int i=0; i<num; i++)
          cout << src[i] << ' ';
       cout << endl;
    }

    int main() {
       int ints[5] = {1, 2, 3, 4, 5};
       double doubles[4] = {0.1, 0.2, 0.3, 0.4};
       char letters[6] = {'a', 'b', 'c', 'd', 'e', 'f'};

       int *ints2 = copy(ints,5);
       double *doubles2 = copy(doubles,4);
       char *letters2 = copy(letters,6);

       print(ints,5);
       print(ints2,5);

       print(doubles,4);
       print(doubles2,4);

       print(letters,6);
       print(letters2,6);
    }

Yep. It works:

    1 2 3 4 5
    1 2 3 4 5
    0.1 0.2 0.3 0.4
    0.1 0.2 0.3 0.4
    a b c d e f
    a b c d e f

Do you see further simplifications? No, unless there is some way to parameterize the type of the arguments when you define the functions.

Templates to the rescue!

    #include <iostream>
    using namespace std;

    template <typename T>
    T *copy(T *src, int num) {
       T *dest = new T[num];
       for (int i=0; i<num; i++)
          dest[i] = src[i];
       return dest;
    }

    template <typename T>
    void print(T *src, int num) {
       for (int i=0; i<num; i++)
          cout << src[i] << ' ';
       cout << endl;
    }

    int main() {
       int ints[5] = {1, 2, 3, 4, 5};
       double doubles[4] = {0.1, 0.2, 0.3, 0.4};
       char letters[6] = {'a', 'b', 'c', 'd', 'e', 'f'};

       int *ints2 = copy(ints,5);
       double *doubles2 = copy(doubles,4);
       char *letters2 = copy(letters,6);

       print(ints,5);
       print(ints2,5);

       print(doubles,4);
       print(doubles2,4);

       print(letters,6);
       print(letters2,6);
    }

Look at all the places where T was used and was not used in copy().


Templates can also be used to avoid redefining classes for different types. Here’s an example. Let’s say we want to keep track of either integer or floating-point 2-D points:

    #include <iostream>
    using namespace std; 

    class PointInt {
    private:
       int x, y;
    public:
       PointInt(const int xarg, const int yarg) : x(xarg), y(yarg) {
       }
       void setx(const int xarg) {
          x = xarg;
       }
       void sety(const int yarg) {
          y = yarg;
       }
       int getx() const {
          return x;
       }
       int gety() const {
          return y;
       }
    }; 

    class PointDouble {
    private:
       double x, y;
    public:
       PointDouble(const double xarg, const double yarg) : x(xarg), y(yarg) {
       }
       void setx(const double xarg) {
          x = xarg;
       }
       void sety(const double yarg) {
          y = yarg;
       }
       double getx() const {
          return x;
       }
       double gety() const {
          return y;
       }
    };

    int main() {
       PointInt pi(1,2);
       PointDouble pd(3.4,2.5);

       cout << '(' << pi.getx() << ',' << pi.gety() << ')' << endl;
       cout << '(' << pd.getx() << ',' << pd.gety() << ')' << endl;

    }

When run, we see this output.

    (1,2)
    (3.4,2.5)

Can we get rid of the duplications between PointInt and PointDouble? Only if there is a way to parameterize the type of the class.

Templates to the rescue!

    #include <iostream>
    using namespace std; 

    template <typename T>
    class Point {
    private:
       T x, y;
    public:
       Point(const T xarg, const T yarg) : x(xarg), y(yarg) {
       }
       void setx(const T xarg) {
          x = xarg;
       }
       void sety(const T yarg) {
          y = yarg;
       }
       T getx() const {
          return x;
       }
       T gety() const {
          return y;
       }
    };

    int main() {
       // This next statement doesn’t work.
       // For templates classes, must specify the type.
       // Point pi(1,2);

       Point<int> pi(1,2);
       Point<double> pd(3.4,2.5);

       cout << '(' << pi.getx() << ',' << pi.gety() << ')' << endl;
       cout << '(' << pd.getx() << ',' << pd.gety() << ')' << endl;
    }

You can have more than one parameter—just be sure you use the right type in the right place.

    #include <iostream>
    using namespace std; 

    template <typename Tx, typename Ty>
    class Point {
    private:
       Tx x;
       Ty y;
    public:
       Point(const Tx xarg, const Ty yarg) : x(xarg), y(yarg) {
       }
       void setx(const Tx xarg) {
          x = xarg;
       }
       void sety(const Ty yarg) {
          y = yarg;
       }
       Tx getx() const {
          return x;
       }
       Ty gety() const {
          return y;
       }
    };

    int main() {
       // This next statement doesn’t work.  For templates classes, must specify the type.
       // Point pi(1,2);

       Point<int,int> pi(1,2);
       Point<double,double> pd(3.4,2.5);

       Point<int,double> pid(5, 6.42);

       cout << '(' << pi.getx()  << ',' << pi.gety()  << ')' << endl;
       cout << '(' << pd.getx()  << ',' << pd.gety()  << ')' << endl;
       cout << '(' << pid.getx() << ',' << pid.gety() << ')' << endl;
    }

Output is

    (1,2)
    (3.4,2.5)
    (5,6.42)

You can also include a constant integer to specify things like the size of an array, and can have a default value.

    #include <iostream>
    using namespace std; 

    template <typename T, int N = 10>
    class Bunch {
    private:
       T data[N];
    public:
       Bunch() {}

       void setData(int index, const T &item) {
          data[index] = item;
       }

       friend ostream &operator<< (ostream &out, const Bunch<T,N> &b) {
          for (int i=0; i<N; i++)
             out << b.data[i] << ' ';
          return out;
       }
    };

    int main() {
       Bunch<char,5> b;

       b.setData(0,'a');
       b.setData(1,'b');
       b.setData(2,'c');
       b.setData(3,'d');
       b.setData(4,'e');

       cout << b << endl;
    }

Produces the output:

    a b c d e

Adding these lines at the end of main()

    Bunch<int> nums;

    nums.setData(0,11);
    nums.setData(1,22);

    cout << nums << endl;

produces:

    a b c d e
    11 22 4196139 0 9376000 0 4197223 0 9339136 0

Why the funky numbers?

Now, back to the points. We have parameterized the types of x and y in Point. Now, how do we make a collection, like an array, of Point objects, when Point itself is parameterized?

    #include <iostream>
    using namespace std; 

    template <typename T>
    class Point {
    private:
       T x, y;
    public:
       Point() : x(0), y(0) {
       }
       Point(const T xarg, const T yarg) : x(xarg), y(yarg) {
       }
       void setx(const T xarg) {
          x = xarg;
       }
       void sety(const T yarg) {
          y = yarg;
       }
       T getx() const {
          return x;
       }
       T gety() const {
          return y;
       }
    }; 

    template <typename T, template <typename> class P>     // Whoa ...
    class Points {
    private:
       P<T> *data;
       int number;
    public:
       Points(const int num) : number(num), data(new P<T>[num]) {}

       void set(int index, const T &x, const T &y) {
          data[index].setx(x);
          data[index].sety(y);
       }

       friend ostream &operator<<(ostream &out, Points<T,P> &p) {
          for (int i=0; i<p.number; i++)
             out << " (" << p.data[i].getx() << ',' << p.data[i].gety() << ')';
          return out;
       }
    };

    int main() {
       Points<int,Point> points(3);

       points.set(0,1,2);
       points.set(1,10,20);
       points.set(2,30,40);

       cout << points << endl;

       Points<double,Point> pointsd(3);

       pointsd.set(0,1.1,2.2);
       pointsd.set(1,10.1,20.2);
       pointsd.set(2,30.1,40.2);

       cout << pointsd << endl;
    }
    (:sourceend:)

Output is:

    (1,2) (10,20) (30,40)
    (1.1,2.2) (10.1,20.2) (30.1,40.2)

Tough slogging ... but cool!

Modified: 2012-04-06T18:29

User: Guest

Check: HTML CSS
Edit History Source
Apply to CSU | Contact CSU | Disclaimer | Equal Opportunity
Colorado State University, Fort Collins, CO 80523 USA
© 2015 Colorado State University
CS Building