CS253: Software Development with C++

Spring 2023

Typename

Show Lecture.Typename as a slide show.

CS253 Typename

🍨

Requirement

set<bool> sb;
auto &id = typeid(sb.size());
if (id == typeid(unsigned short)) cout << "unsigned short\n";
if (id == typeid(unsigned))       cout << "unsigned int\n";
if (id == typeid(0UL))            cout << "unsigned long\n";
if (id == typeid(0ULL))           cout << "unsigned long long\n";
if (id == typeid(size_t))         cout << "size_t\n";
unsigned long
size_t

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

Second pass

template <class T>
struct Answer {
    typename set<T>::size_type value = 42;
};

int main() {
    Answer<short> doug;
    cout << doug.value;
}
42
     using newtype = typename oldtype;
     typedef typename oldtype newtype;

Why?

“But, Logan, 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

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!