Show Lecture.Typename as a slide show.
CS253 Typename
Requirement
- I want to create a templated class that contains a value.
- The value should be the same type
set<T>::size()
returns.
set::size_type
is an alias for the type that set::size()
returns.
- This is often, but not necessarily size_t.
- E.g.,
set<bool>::size()
is always 0, 1, or 2, so a template
specialization for set<bool>
could use a small
(e.g., 16-bit) unsigned int to store the size, and hence
set<bool>::size_type
might be unsigned short.
set<bool>::size_type z = 0;
cout << sizeof(z) << endl;
throw z;
8
terminate called after throwing an instance of 'unsigned long'
SIGABRT: Aborted
First pass
template <class T>
struct Answer {
set<T>::size_type value = 42; // 🦡
};
int main() {
Answer<short> doug;
cout << doug.value;
}
c.cc:3: error: need ‘typename’ before ‘std::set<T>::size_type’ because
‘std::set<T>’ is a dependent scope
- The compiler doesn’t know that, for all possible types
T
,
set<T>::size_type
will be a type, and not a method
or a variable.
- Sure, we know that
::size_type
is always an
unsigned integer type, but we’re smart.
Second pass
template <class T>
struct Answer {
typename set<T>::size_type value = 42;
};
int main() {
Answer<short> doug;
cout << doug.value;
}
42
- The keyword typename, meaning “trust me, it’s a type”,
is needed for a dependent type, i.e., a type that depends
on a template parameter
T
in set<T>::size_type
.
- This also applies to new types defined by typedef or using:
using newtype = typename oldtype;
typedef typename oldtype newtype;
- Perhaps, someday, typename will no longer be required.
Why?
- OK, fine, but nobody would ever want to actually do that.
- Consider a class that’s exactly like a set, execpt that
it’s fussy.
- Specifically, like a fussy child, it just doesn’t like
one particular food.
- It doesn’t like … ice cream! 😲
“But, Jack, there are other reasons for not eating ice cream! How dare you compare all people who don’t eat ice cream to children!” 🙄
- Thank you. We all know this.
- Some people avoid ice cream for medical or ethical reasons.
- This was one possible reason. It’s not all about you.
- A → B does not mean that B → A.
Practical use
template <class T>
class fussyset {
set<T> store;
public:
using iterator = set<T>::iterator; // 🦡
void insert(T val) { if (val != "ice cream") store.insert(val); }
iterator begin() const { return store.begin(); }
iterator end() const { return store.end(); }
};
int main() {
fussyset<string> fs;
for (auto food : {"apple", "cereal", "ice cream", "apple"})
fs.insert(food);
for (auto val : fs)
cout << val << '\n';
}
c.cc:5: error: need ‘typename’ before ‘std::set<T>::iterator’ because
‘std::set<T>’ is a dependent scope
Practical use
template <class T>
class fussyset {
set<T> store;
public:
using iterator = typename set<T>::iterator;
void insert(T val) { if (val != "ice cream") store.insert(val); }
iterator begin() const { return store.begin(); }
iterator end() const { return store.end(); }
};
int main() {
fussyset<string> fs;
for (auto food : {"apple", "cereal", "ice cream", "apple"})
fs.insert(food);
for (auto val : fs)
cout << val << '\n';
}
apple
cereal
Has-a vs. Is-a
- The previous class did not have an is-a relationship.
- Instead, a
fussyset
has-a set<T>
, with forwarding methods.
- It can be done using is-a, however.
Is-a
template <class T>
class fussyset : private set<T> { // PRIVATE inheritance‽
using super = set<T>;
public:
using iterator = typename super::iterator;
void insert(T val) { if (val != "ice cream") super::insert(val); }
iterator begin() const { return super::begin(); }
iterator end() const { return super::end(); }
};
int main() {
fussyset<string> fs;
for (auto food : {"apple", "cereal", "ice cream", "apple"})
fs.insert(food);
for (auto val : fs)
cout << val << '\n';
}
apple
cereal
Those forwarding methods are still a pain, and WET,
repeating the return types, constness, and arguments.
Is-a
template <class T>
class fussyset : private set<T> {
using super = set<T>;
public:
using super::iterator, super::begin, super::end; // public
void insert(T val) { if (val != "ice cream") super::insert(val); }
};
int main() {
fussyset<string> fs;
for (auto food : {"apple", "cereal", "ice cream", "apple"})
fs.insert(food);
for (auto val : fs)
cout << val << '\n';
}
apple
cereal
A new use of using! Much better. This way, we know that the
types & arguments of the fussyset
methods are the same as those of
the set<T>
methods. No typename, either!