Example: IntVector
to Vector<T>
Here's a bigger example, where we've converted our IntVector
class into a Vector<T>
class template.
- Open
IntVector
Code in Online GDB (original code) - Open
Vector<T>
Code in Online GDB (revised code)
Open both at once (ideally, try to have them side by side in separate windows) and compare the code to see how it has changed.
Gah! That was a lot of
template <typename T>
repeated everywhere. I thought we were trying to avoid copying and pasting code!…
Hay! There was something else I didn't expect. What was the deal with
typename Vector<T>::iterator ?
I expected just
Vector<T>::iterator .
Oh, well spotted! You've noticed something pretty subtle that we have to get used to.
Come on, Bjarne, tell us it's part of C++'s heritage.
No, this doesn't come from our C history. This is… all original to C++.
Okay, folks, there are two ways to look at things…
When to Add typename
—The Practical View
One way to look at this code is to say, “This is simply some bizarre C++ nonsense, and you just have to sometimes scatter the word typename
into your code”. The good news is, if you don't say typename
where you need to say it, the compiler will give you a pretty clear error to tell you how to fix the issue.
These errors look like
./vector-private.hpp:114:1: error: missing 'typename' prior to dependent type name 'Vector<T>::iterator' Vector<T>::iterator Vector<T>::end() { ^~~~~~~~~~~~~~~~~~~ typename 1 error generated.
The compiler is pretty clearly saying “put typename
here”. Do as it says, and all will be well.
When to Add typename
—The Why
Suppose we have a template type parameter T
. Do you think the following are types or values?
T::const_iterator
T::max_size
T::snoozlewuzzle
You probably think the first one is a type (for an iterator), and the second one is a value (probably a size_t
value). But what about the third one?
I know what a “snoozlewuzzle” is, but I can't reveal that knowledge to you.
We don't know what T::snoozlewuzzle
is. Also, we don't even know for sure what T::const_iterator
is. Some joker could have defined a class that has a member variable named const_iterator
, and, because we don't know what T
is, we can't be sure that T
isn't such a class, even though it would be pretty weird to have done that.
So, for all these cases, the compiler can't be sure what it's dealing with. So it adopts a very simple rule: if it doesn't know what something is for sure, always assume that something is not a type.
When the thing is a type, we have to put typename
in front of it to clue the compiler in. But we only need to do it when there is a template type argument involved.
But it wasn't just
T::iterator
, it wasVector<T>::iterator
. Why can't it look at theVector
class template and know?It is still a dependent name and there is also template specialization to consider…
Let's not get too far into the weeds here. The big picture is that if you have a template type parameter in there, you probably need
typename
.Until C++20, when we've made the compiler smarter.
The compiler was already smarter. It has to give us good error messages about where we were supposed to put
typename
.Yes. That's one reason we're relaxing the rules a bit.
Unfortunately, C++20 support in current compilers is still a bit patchy, so we're sticking to C++17 in CS 70 for now.
But some day, most of this nonsense will go away.
I'm excited for that day!
Overall Tips
The compiler is actually super helpful if you forget to put typename
where you need it. It will tell you exactly where to put it. So don't worry too much about it.
On the other hand, if you get all overzealous and throw typename
in places where it isn't needed, you'll totally confuse the poor compiler which will assume you're a clever C++ pro and doing weird things on purpose. It will then give you a very confusing error message that will be hard to figure out.
You'll probably want to have an example like this one nearby when you first start out making class templates so you check your code against the example.
(When logged in, completion status appears here.)