CS253: Software Development with C++

Spring 2020

Templates

Show Lecture.Templates as a slide show.

CS253 Templates

CS253 Templates

This presentation was stolen from Prof. Chuck Anderson.

Initial Code

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

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

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

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

int main() {
    double doubles[] = {0.1, 0.2, 0.3, 0.4};
    double *doubles2 = copyDoubles(doubles, 4);
    char *letters2 = copyLetters("bonehead", 8);
    printDoubles(doubles2, 4);
    printLetters(letters2, 8);
}
0.1
0.2
0.3
0.4
b
o
n
e
h
e
a
d

Simplify

Simplified

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

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

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

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

int main() {
    double doubles[] = {0.1, 0.2, 0.3, 0.4};
    double *doubles2 = copyDoubles(doubles, 4);
    char *letters2 = copyLetters("bonehead", 8);
    print(doubles2, 4);
    print(letters2, 8);
}
0.1
0.2
0.3
0.4
b
o
n
e
h
e
a
d

Simplify Again

Simplified Again

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

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

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

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

int main() {
    double doubles[] = {0.1, 0.2, 0.3, 0.4};
    double *doubles2 = copy(doubles, 4);
    char *letters2 = copy("bonehead", 8);
    print(doubles2, 4);
    print(letters2, 8);
}
0.1
0.2
0.3
0.4
b
o
n
e
h
e
a
d

Simplified more?

Templates to the rescue!

template <typename T>
T *copy(const 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 <class T>            // old-fashioned syntax
void print(T *src, int num) {
    for (int i=0; i<num; i++) cout << src[i] << '\n';
}

int main() {
    double doubles[] = {0.1, 0.2, 0.3, 0.4};
    double *doubles2 = copy(doubles, 4);
    char *letters2 = copy("bonehead", 8);
    print(doubles2, 4);
    print(letters2, 8);
}
0.1
0.2
0.3
0.4
b
o
n
e
h
e
a
d

Templates to the rescue!

template <typename T>
T *copy(const 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 <class T>            // old-fashioned syntax
void print(T *src, int num) {
    for (int i=0; i<num; i++) cout << src[i] << '\n';
}

int main() {
    double doubles[] = {0.1, 0.2, 0.3, 0.4};
    double *doubles2 = copy(doubles, 4);
    char *letters2 = copy("bonehead", 8);
    print(doubles2, 4);
    print(letters2, 8);
}
0.1
0.2
0.3
0.4
b
o
n
e
h
e
a
d

Templates to the rescue!

template <typename T>
T *copy(const 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 <class T>            // old-fashioned syntax
void print(T *src, int num) {
    for (int i=0; i<num; i++) cout << src[i] << '\n';
}

int main() {
    double doubles[] = {0.1, 0.2, 0.3, 0.4};
    double *doubles2 = copy(doubles, 4);
    char *letters2 = copy("bonehead", 8);
    print(doubles2, 4);
    print(letters2, 8);
}
0.1
0.2
0.3
0.4
b
o
n
e
h
e
a
d

Templates to the rescue!

template <typename T>
T *copy(const 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 <class T>            // old-fashioned syntax
void print(T *src, int num) {
    for (int i=0; i<num; i++) cout << src[i] << '\n';
}

int main() {
    double doubles[] = {0.1, 0.2, 0.3, 0.4};
    double *doubles2 = copy(doubles, 4);
    char *letters2 = copy("bonehead", 8);
    print(doubles2, 4);
    print(letters2, 8);
}
0.1
0.2
0.3
0.4
b
o
n
e
h
e
a
d

Templates to the rescue!

template <typename T>
T *copy(const 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 <class T>            // old-fashioned syntax
void print(T *src, int num) {
    for (int i=0; i<num; i++) cout << src[i] << '\n';
}

int main() {
    double doubles[] = {0.1, 0.2, 0.3, 0.4};
    double *doubles2 = copy(doubles, 4);
    char *letters2 = copy("bonehead", 8);
    print(doubles2, 4);
    print(letters2, 8);
}
0.1
0.2
0.3
0.4
b
o
n
e
h
e
a
d

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

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 = 5>
class Handful {                       // Much like std::array
    T data[N];
  public:
    void setData(int index, const T &item) { data[index] = item; }
    friend ostream &operator<< (ostream &out, const Handful<T, N> &h) {
        for (int i=0; i<N; i++)
            out << h.data[i] << '\n';
        return out;
    }
};

int main() {
    Handful<char, 3> h;
    h.setData(0, 'a');
    h.setData(1, 'b');
    h.setData(2, 'c');
    cout << h;
}
a
b
c

A less successful alternative

template <typename T, int N = 5>
class Handful {
    T data[N];
  public:
    void setData(int index, const T &item) { data[index] = item; }
    friend ostream &operator<< (ostream &out, const Handful<T, N> &h) {
        for (int i=0; i<N; i++)
            out << h.data[i] << '\n';
        return out;
    }
};

int main() {
    Handful<int> h;
    h.setData(0, 42);
    h.setData(1, 365);
    cout << h;
}
42
365
4196032
0
776294736

Range of template parameters

What can a template parameter be?

If the template parameter is typename or class, the actual parameter 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, integral values, and pointers.

Understand the difference

This is fine:

template<typename T>
class Foo {
  public:
    T data[3] = {1,2,3};
};

int main() {
    Foo<double> f;
    cout << "😀\n";
}
😀

Understand the difference

This won’t work:

template<double D>
class Foo {
  public:
    double datum = D;
};

int main() {
    Foo<3.16227766> f;
    cout << "😟\n";
}
c.cc:1: error: 'double' is not a valid type for a template non-type parameter

Standard containers

    template <typename T>
    class vector {
        …
    };

Templated using declarations

A using declaration can be templated to create a new type:

template<class T>
using Counter = map<T, size_t>;

int main() {
    Counter<string> c;
    c["alpha"]++;
    c["beta"]++;
    c["alpha"]++;
    for (auto v : c)
        cout << v.first << ' ' << v.second << '\n';
}
alpha 2
beta 1

Templated variables

God help us, even variables can be templated:

template <typename T>
constexpr T pi = T(3.14159265358979323846264);

template <>
const string pi<string> = "π";

int main() {
    cout << pi<long double> << '\n';
    cout << pi<int> << '\n';
    cout << pi<string> << '\n';
}
3.14159
3
π

What can be templated?

What can be templated?

Function specialization

You can have both a templated function and non-templated versions:

template <typename T>
void out(T n) {
    cout << "whatever: " << n << '\n';
}

void out(double n) {
    cout << "double: " << n << '\n';
}

int main() {
    out(11);
    out(2.22);
    out(3.33F);
    out("chocolate");
}
whatever: 11
double: 2.22
whatever: 3.33
whatever: chocolate

The ordinary function is a better match than the templated function.

Template specialization

template <typename T>
class Dozen {
    T data[12];
  public:
    size_t size() const { return sizeof(data); }
};

int main() {
    Dozen<int> di;
    Dozen<void> dv;
    cout << di.size() << ' ' << dv.size() << '\n';
}
c.cc: In instantiation of 'class Dozen<void>':
c.cc:10:   required from here
c.cc:3: error: creating array of 'void'

Sometimes, a template doesn't work for all types.

Template specialization

You can have both a templated class and non-templated versions:

template <typename T>
class Dozen {
    T data[12];
  public:
    size_t size() const { return sizeof(data); }
};

template <>
struct Dozen<void> {
    size_t size() const { return 0; }
};

int main() {
    Dozen<int> di;
    Dozen<void> dv;
    cout << di.size() << ' ' << dv.size() << '\n';
}
48 0