CS 70

Class Templates

  • Pig speaking

    Are there MORE kinds of templates besides function templates?

  • LHS Cow speaking

    Yes, there are class templates, too.

  • Duck speaking

    I think we've seen them already. When we say std::vector<int> etc., we're using a class template, right?

  • LHS Cow speaking

    That's right. Class templates are especially useful for data structures that might store different types of items.

Here's a simple class that stores a pair of ints.

intpair.hpp (include guard omitted):

class IntPair {
  public:
    IntPair(int f, int s);
    int getFirst() const;
    int getSecond() const;
    void setFirst(int f);
    void setSecond(int s);
  private:
    int first_;
    int second_;
};

And here it is converted into a class template. Now when we want a pair like IntPair, we can say Pair<int,int>, but we can specify any types we want for the data it stores—it isn't locked to storing ints (e.g., a Pair<std::string,bool> will be possible too).

pair.hpp (include guard omitted):

template <typename F, typename S>
class Pair {
  public:
    Pair(const F& first, const S& second);
    const F& getFirst() const;
    const S& getSecond() const;
    void setFirst(const F& f);
    void setSecond(const S& s);
  private:
    F first_;
    S second_;
};

#include "pair-private.hpp"

pair-private.hpp

template <typename F, typename S>
Pair<F, S>::Pair(const F& first, const S& second) :
    first_{first},
    second_{second} {
}

template <typename F, typename S>
const F& Pair<F, S>::getFirst() const {
    return first_;
}

template <typename F, typename S>
void Pair<F, S>::setFirst(const F& f) {
    first_ = f;
}

Things to Notice

  • When you define a member function, you need to say what class it's part of. Because these functions are part of a class template, that means…
    • Every single member function definition needs to have the same template preamble as the class itself did.
      • e.g., template
    • The class name in the function name needs template arguments!
      • e.g., Pair<F, S>:: rather than Pair::

And here is some code that uses the Pair template.

main.cpp:

#include "pair.hpp"

int main() {
    Pair<float, bool> p1{4.2, true};
    float x = p1.getFirst();

    Pair<int, int*>* p2 = new Pair<int, int*>{6, nullptr};
    int* y = p2->getSecond();

    Pair<Pair<float, bool>, Pair<int, int*>*> p3{p1, p2};

    delete p2;
}

Things to Notice

  • There is no class named Pair!
    • Pair is a class template, and a class template is not a class, it's a recipe for making individual classes, such as Pair<int,int>.
    • The same is true for other class templates, such as std::vector and std::list.
    • A template name is not a type without template arguments.
    • If you try to say std::vector myVector; you will get an error.
    • Instead, be specific, say: std::vector<int> myVector;
  • Duck speaking

    But why can't C++ automatically figure out the types like it did for functions? I think we ought to be able to say

    Pair p1{4.2, true};
    

    and have it figure out that p1 is a Pair<double,bool>.

  • Rabbit speaking

    It can! Class-template–argument deduction is a feature that was added in C++17.

  • LHS Cow speaking

    But in CS 70, we prefer to be explicit and say what we mean. Too much stuff happening automagically can be confusing.

  • RHS Cow speaking

    And learning about when deduction works and when it doesn't is a whole other rabbit hole! Let's not get too into the weeds.

You Explore and Experiment!

You can run an expanded variation of the above code online by clicking on

Look over the code:

  • Compare intpair.hpp with pair.hpp.
  • Compare intpair.cpp with pair-private.hpp.
  • Read over main.cpp and run the program.

Expanding the Code

Now expand the code by adding a combine() member function that returns a value of type F by using the + operator to combine first_ and second_. (What combine() actually does will depend on the definition of + for the types in the pair!)

You'll need to declare combine() in pair.hpp and implement it in pair-private.hpp.

Once you've added it, you can try it out with code like

std::cout << "pair0.combine(): " << pair0.combine() << std::endl;
std::cout << "pair1.combine(): " << pair1.combine() << std::endl;
std::cout << "pair2.combine(): " << pair2.combine() << std::endl;

What is the value of pair2.combine()?

If you need a working version of combine(), you can get one from

Now try adding:

std::cout << "pair3.combine(): " << pair3.combine() << std::endl;

to try to combine (3.14, 2.718) with (4.2, true).

What happens, and does anything about that surprise you?

It produces an error:

In file included from pair.hpp:30:0,
                 from main.cpp:2:
pair-private.hpp: In instantiation of ‘F Pair<F, S>::combine() const [with F = Pair<double, double>; S = Pair<float, bool>]’:
>main.cpp:75:55:   required from here
pair-private.hpp:45:19: error: no match for ‘operator+’ (operand types are ‘const Pair’ and ‘const Pair’)
     return first_ + second_;
            ~~~~~~~^~~~~~~~~

The compiler is saying that it can't stamp out a combine function when F is Pair<double, double> and S is Pair<float, bool> because there is no + operator for these two argument types.

  • Goat speaking

    Meh. An error from C++. I'm not surprised.

  • Horse speaking

    Hay! I am a bit surprised, actually. Because it only errored out when we added that line. Why is it only erroring out now? Our class template always had a combine() function; we weren't using it, but it was there. Why did it only blow up when we wrote code that used a function that we already had?

  • Bjarne speaking

    C++'s template system is lazy. It only instantiates templates when it has to. So even though it had stamped out the Pair<Pair<double, double>, Pair<float, bool>> class, it hadn't bothered to make that class's combine() function until you tried to use it.

  • Goat speaking

    Laziness is an idea I can get behind.

  • LHS Cow speaking

    So now you've seen class templates… The syntax is a bit clunky, but it's quite flexible and powerful.

  • Cat speaking

    This all seems pretty straightforward.

  • Hedgehog speaking

    Uh… Okay… Can we have a bigger example? Maybe of a whole class?

  • LHS Cow speaking

    Sure. Let's convert our IntVector class.

(When logged in, completion status appears here.)