CS253

CS253: Software Development with C++

Spring 2017

Templates

See this page as a slide show

Templates

CS253 Templates

This presentation was stolen from Chuck Anderson.

Initial Code

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 printDoubles(double *src, int num) {
    for (int i=0; i<num; i++) cout << src[i] << '\n';
}

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

int main() {
    double doubles[4] = {0.1, 0.2, 0.3, 0.4};
    char letters[6] = {'a', 'b', 'c', 'd', 'e', 'f'};
    double *doubles2 = copyDoubles(doubles,4);
    char *letters2 = copyLetters(letters,6);
    printDoubles(doubles2,4);
    printLetters(letters2,6);
}
0.1
0.2
0.3
0.4
a
b
c
d
e
f

Simplify

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

Simplified

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(double *src, int num) {
    for (int i=0; i<num; i++) cout << src[i] << '\n';
}

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

int main() {
    double doubles[4] = {0.1, 0.2, 0.3, 0.4};
    char letters[6] = {'a', 'b', 'c', 'd', 'e', 'f'};
    double *doubles2 = copyDoubles(doubles,4);
    char *letters2 = copyLetters(letters,6);
    print(doubles2,4);
    print(letters2,6);
}
0.1
0.2
0.3
0.4
a
b
c
d
e
f

Simplify Again

Can we do the same for the copy functions? Sure, why not?

Simplified Again

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(double *src, int num) {
    for (int i=0; i<num; i++) cout << src[i] << '\n';
}

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

int main() {
    double doubles[4] = {0.1, 0.2, 0.3, 0.4};
    char letters[6] = {'a', 'b', 'c', 'd', 'e', 'f'};
    double *doubles2 = copy(doubles,4);
    char *letters2 = copy(letters,6);
    print(doubles2,4);
    print(letters2,6);
}
0.1
0.2
0.3
0.4
a
b
c
d
e
f

Simplified more?

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!

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

template <typename T>               // typename used to be class
void print(T *src, int num) {
    for (int i=0; i<num; i++) cout << src[i] << '\n';
}

int main() {
    double doubles[4] = {0.1, 0.2, 0.3, 0.4};
    char letters[6] = {'a', 'b', 'c', 'd', 'e', 'f'};
    double *doubles2 = copy(doubles,4);
    char *letters2 = copy(letters,6);
    print(doubles2,4);
    print(letters2,6);
}
0.1
0.2
0.3
0.4
a
b
c
d
e
f

See where T was and was not used in copy().

Classes

Say we want to keep track of either integer or real 2-D points:

class PointInt {
    int x, y;
  public:
    PointInt(int xarg, int yarg) : x(xarg), y(yarg) { }
    int getx() const { return x; }
    int gety() const { return y; }
};

class PointDouble {
    double x, y;
  public:
    PointDouble(double xarg, double yarg) : x(xarg), 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() << ")\n"
         << '(' << pd.getx() << ',' << pd.gety() << ")\n";
}
(1,2)
(3.4,2.5)

You saw this coming

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

Template class

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

int main() {
    // This statement fails—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() << ")\n"
         << '(' << pd.getx() << ',' << pd.gety() << ")\n";
}
(1,2)
(3.4,2.5)

Multiple template parameters

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

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

int main() {
    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()  << ")\n"
         << '(' << pd.getx()  << ',' << pd.gety()  << ")\n"
         << '(' << pid.getx() << ',' << pid.gety() << ")\n";
}
(1,2)
(3.4,2.5)
(5,6.42)

Integer template parameters

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

Example

template <typename T, int N = 10>
class Bunch {                       // Much like C++11 std::array
    T data[N];
  public:
    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] << '\n';
        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;
}
a
b
c
d
e

A less successful alternative

template <typename T, int N = 10>
class Bunch {
    T data[N];
  public:
    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] << '\n';
        return out;
    }
};

int main() {
    Bunch<int> b;
    b.setData(0, 42);
    b.setData(1, 365);
    cout << b;
}
42
365
0
0
4196560
0
4196032
0
-1172330976
32764

Range of template parameters

What can a template parameter be?

Note that type doesn’t have to be a built-in boring type like int or float. It can be a class type, or even filled-out templated type such as set<long>.

Range of template parameters

What can’t a template parameter be?

Remember, it’s a compile-time parameter. Types, integer constants, true, and false. That’s it!

Standard containers

    template <typename T>
    class vector {
        …
    };

What can be templated?

What can be templated?

Modified: 2017-03-31T22:56

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