Step 1 of 7 · Reading · ~3 min
Learn
Classes, Inheritance, and Templates
Templates Introduction
A template is a recipe the compiler turns into real code once you name a type. By the end of this lesson you can write a function template, write a class template whose members have type T, and avoid the default-value trap that makes generic code print 0 where it should not.
Function templates
template <typename T> introduces a type parameter named T. Inside the definition, T stands for whatever type the caller uses.
template <typename T>
T twice(T x) {
return x + x;
}
std::cout << twice(21) << "\n"; // T = int -> 42
std::cout << twice(1.25) << "\n"; // T = double -> 2.5
std::cout << twice(std::string("ab")) << "\n"; // T = std::string -> abab
The compiler reads each call, deduces T from the argument, and generates (instantiates) a separate function for each type: one twice<int>, one twice<double>, one twice<std::string>. template <class T> means exactly the same thing; typename is the usual spelling.
A template only works for types that support what its body does. twice needs +, so calling it with the Point class from the classes lesson is a compile error at that call, and the message points into the template body.
Class templates
The same idea applies to a whole class. Every member can use T, and the user picks T in angle brackets:
template <typename T>
class Tally {
T sum_;
int times_;
public:
Tally() : sum_{}, times_{0} {}
void put(const T& x) { sum_ = sum_ + x; ++times_; }
T sum() const { return sum_; }
int times() const { return times_; }
};
Tally<int> scores;
scores.put(4); scores.put(9);
std::cout << scores.sum() << " " << scores.times() << "\n"; // 13 2
Tally<std::string> letters;
letters.put("x"); letters.put("yz");
std::cout << letters.sum() << "\n"; // xyz
For a class template you write the type yourself: Tally<int>, not Tally. Tally<int> and Tally<std::string> are two unrelated classes stamped from one recipe; you cannot assign one to the other. Taking const T& avoids copying a big T such as a long string, the same reason you used const references in earlier lessons.
The T{} trap
sum_{} is value-initialization: it gives T's "empty" value.
T | T{} |
|---|---|
int | 0 |
double | 0.0 |
std::string | "" |
That is the right start for a sum. It is the wrong start for anything that compares against real data:
template <typename T>
T biggest(const std::vector<T>& v) {
T best{}; // WRONG start
for (const T& x : v)
if (x > best) best = x;
return best;
}
biggest(std::vector<int>{-5, -2}); // returns 0, but -2 was expected
0 was never in the data, yet it wins because every real value is smaller. The fix is to take the starting value from the data itself: begin with the first element, or keep a flag or counter that says "nothing stored yet" and let the first value in unconditionally.
< on std::string compares character codes left to right, so "Zebra" < "apple" (capital letters come first) and "10" < "9".
Common mistakes
- Starting smallest/largest at
T{}. With values5 8 6the minimum comes out0; with-4 -9 -1the maximum comes out0; for words the minimum comes out as an empty string. - Writing
intwhere you meantT. A member declaredint best;compiles forMinMax<int>and fails to compile forMinMax<std::string>. - Expecting dictionary order for words.
Zebra,apple,Mangogive minMango, maxapple; do not lowercase anything, just use<and>. - Forgetting to count. If the counter never moves, the report always says
empty.
Your exercise
Finish the class template MinMax<T>: add its private members and fill in add, count, low and high. The starter already reads the input, builds a MinMax<int> and a MinMax<std::string>, and prints them with a given report function. The most likely bug is seeding the smallest and largest values with T{}.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…