Summary

While the previous lab was entirely dedicated to the subject of C++ function templates, in this session we'll focus on class templates. While there are a lot of similarities to function templates, there are also important differences that have to be looked at. At the end, we'll also scratch the surface of template metaprogramming.

Class templates

Basics

Class templates, just like function templates let you define a pattern for a class to be instantiated with type information and used at some point in your code. The template parameters of a class template can be used at any place where a type is necessary: definition of members, member function signatures, but also in the base class mention.

Probably the most logical example of class templates are things you've been using since the first lab: containers in the STL (Standard Template Library), now part of C++'s std library.

For instance, the definition of the well-known std::vector is:

template<
    class T,
    class Allocator = std::allocator<T>
> class vector;

Here T denotes a generic type of elements to be contained in the vector. Instantiating a vector<int> in your code effectively creates a new class vector with T = int and where the Allocator is the default std::allocator<int>.

As such, a class template acts as a pattern used by the compiler to generate (instantiate) a family of classes depending on the type of the arguments they're called with. A template class is an instantiation of a class template.

The basic syntax of defining a class template is:

template <typename T>
class Foo {
public:
    Foo(T t) : baz(t) {}
    T bar(T t) {
        return this->baz + t;
    }
    T fighter(T t);
private:
    T baz;
};

template <typename T>
T Foo<T>::fighter(T t) {
    return this->baz - t;
}

As you can see, member functions of a class template can also be defined outside the class definition. Using a special syntax, one makes clear that the member function is part of a class template.

Please note that since the compiler does not know what type will be used as T no code is generated for a class template. The code for a template class is generated at the moment of its instantiation with a concrete type.

Instantiation

Using (and therefore instantiating) the above template works as expected:

Foo<int> f(1);
cout << f.bar(2) << endl;

By the way: if you try to be lazy and let the compiler do the work of deducing T from the argument you pass to Foo's constructor you'll get a friendly error message urging you to specify the type yourself (clang++'s error reporting is a tiny bit more "human"):

f.cpp:18:5: error: use of class template 'Foo' requires template arguments
    Foo f(1);
    ^

Remember: class templates, unlike function templates, don't use automatic type deduction!
You'll have to specify the type yourself.

You can see that no code is generated for a class template unless it's instantiated for a specific type using the same experiment you did with function templates, i.e., using nm classtemplate | c++filt:

0000000100001efb unsigned short __GLOBAL__sub_I_main.cpp
0000000100001eb2 unsigned short __static_initialization_and_destruction_0(int, int)
                 U std::ios_base::Init::Init()
                 U std::ios_base::Init::~Init()
0000000100002030 short std::__ioinit
                 U ___cxa_atexit
0000000100000000 T __mh_execute_header
0000000100001ea7 T _main
                 U dyld_stub_binder

Also, for member function: they are only generated when they are used for the first time. In the following example, the main function looks like this:

int main() {
  Foo<int> f(1);
  f.bar(2);
}

You see that no code is generated for the fighter method as it is not used:

0000000100001e7f unsigned short __GLOBAL__sub_I_main.cpp
0000000100001e36 unsigned short __static_initialization_and_destruction_0(int, int)
0000000100001e1e T Foo<int>::bar(int)
0000000100001e06 T Foo<int>::Foo(int)
                 U std::ios_base::Init::Init()
                 U std::ios_base::Init::~Init()
0000000100002040 short std::__ioinit
                 U ___cxa_atexit
0000000100000000 T __mh_execute_header
0000000100001dd4 T _main
                 U dyld_stub_binder

Side note

The fact that two instantiations of the same template with slightly related types gives you totally unrelated template classes might be annoying on a couple of occasions. A standard example of this annoyance is defining a vector of pointers:

std::vector<int*> v;

And trying to pass it to a function declared as:

void f(const std::vector<const int *> & w);

clang++ complains:

f.cpp:14:5: error: no matching function for call to 'f'
    f(v);
    ^
f.cpp:6:10: note: candidate function not viable: no known conversion from 'vector<int *>' to
      'const vector<const int *>' for 1st argument
    void f(const std::vector<const int *> & w) {
         ^

This would be perfectly valid if you'd use a C-style array but gets tricky in the above case. You really need to create a temporary vector, assign element by element and pass that one to f.

The C++ way of solving the above "issue" would be to define f to take a range of constant iterators to work on. This way f will even be agnostic of the underlying container; you could pass a deque instead of a vector if you prefer without rewriting f.

Default template arguments

Default template arguments for class templates is similar to the default template arguments for function templates, as discussed in the last session. Remember this example from above:

template<
    class T,
    class Allocator = std::allocator<T>
> class vector;

Here std::allocator<int> is the default Allocator argument. This second template parameter is there for many of the standard containers such as std::stack, std::list, etc.

Now that you are familiar with class templates, can you change the default template arguments example of last session, so it can deal with other types as well?

class CustomComparatorLess {
public:
    bool operator() (const int& i, const int& j) { return (i < j); };
};

class CustomComparatorGreater {
public:
    bool operator() (const int& i, const int& j) { return (i > j); };
};

template<typename C = CustomComparatorLess>
void customSort(std::vector<int>& v) {
    C cmp;
    std::sort(v.begin(), v.end(), cmp);
    for (int val: v)
        std::cout << val << std::endl;
};

int main() {
    std::vector<int> vInt = {3, 6, 4, 5, 1, 6};
    std::vector<double> vDouble = {3.2, 3.1, 6.1, 6.5, 4.0, 5.3, 1.1, 6.2};
    customSort<int>(vInt);
    customSort<double>(vDouble); // Make this possible!
}

(Look at the reference solution for exercise 8.1 for inspiration.)

Specialization

Last session, we looked to specialization in the case of function templates. With class templates, there is an important difference: both full and partial specialization are possible, while function templates only allow full specialization (as seen in last session).

While full specialization refers to a primary template with all template parameters substituted, partial specialization refers to a primary template with substitution of only some template parameters.

Assuming following revisited Foo class template:

template <typename T, typename U>
class Foo {
public:
    Foo(T b) { this->baz = b; };
    T bar(T, U);
private:
    T baz;
};

template <typename T, typename U>
T Foo<T, U>::bar(T t, U u) {
    return static_cast<T>(t + u + this->baz);
}

Full specialization

Full specialization would look like this:

#include "Foo.hpp"

template <>
class Foo<int, double> {
public:
    Foo(int b) { this->baz = b; };
    int bar(int t, double u);
private:
    int baz;
};

// no template<> here!
int Foo<int, double>::bar(int t, double u) {
    return static_cast<int>(ceil(t + u + this->baz)); // for int and double, first ceil() the result
}

Notice the syntactically missing template<> above the member function bar: this is not used for a member of a specialization, see here.

By the way, it is also possible to apply full specialization to the member function only:

#include "Foo.hpp"

template <> // Now, we HAVE to use this!
int Foo<int, double>::bar(int t, double u) {
    return static_cast<int>(ceil(t + u + this->baz));
}

Here we do not specialize the rest of the class template, only the member function. You can find a list here of things that can be fully specialized.

More information on full specialization:

Partial specialization

Partial specialization only substitutes some parameters of the primary template. Using the same Foo class template as from the full specialization examples, partial specializations can be:

#include "Foo.h"
#include <iostream>
#include <cmath>

template <typename U>
class Foo<int, U> { // First one has to be int, second argument is "open"
public:
    Foo(int b) { this->baz = b; };
    int bar(int, U);
private:
    int baz;
};

template <typename U>
int Foo<int, U>::bar(int t, U u) {
    std::cout << "Picked Foo<int, U>::bar" << std::endl;
    return static_cast<int>(ceil(t + u + this->baz));
}

template <typename U>
class Foo<int, U*> { // First one has to be int, second argument is "open" but has to be a pointer
public:
    Foo(int b) { this->baz = b; };
    int bar(int, U*);
private:
    int baz;
};

template <typename U>
int Foo<int, U*>::bar(int t, U* u) {
    std::cout << "Picked Foo<int, U*>::bar" << std::endl;
    return static_cast<int>(ceil(t + *u + this->baz));
}

template <typename U>
class Foo<double, U> { // First one has to be double, second argument is "open"
public:
    Foo(double b) { this->baz = b; };
    double bar(double, U);
private:
    double baz;
};

template <typename U>
double Foo<double, U>::bar(double t, U u) {
    std::cout << "Picked Foo<double, U>::bar" << std::endl;
    return t + u + this->baz;
}

int main() {
    Foo<int, int> foo(3);
    std::cout << foo.bar(2.3, 5.9) << std::endl;

    int x = 5;
    Foo<int, int*> foo2(3);
    std::cout << foo2.bar(2.3, &x) << std::endl;

    Foo<double, int> foo3(3.9);
    std::cout << foo3.bar(2.3, 5) << std::endl;

    Foo<float, int> foo4(3.4);
    std::cout << foo4.bar(2.3, 5) << std::endl;
    // Which one is executed & why?

    return 0;
}

As you can see the syntax of is partial specialization is slightly different (from the fully specialized syntax): template <parameter-list> (in this case template <typename U>), followed by an argument list where the arguments in this list may depend on parameters in the specialization parameter list.

In this example, these partial specializations use a template parameter U, but they fix the first template parameter of the primary template. On top of that, the second partial specialization states that the second parameter should be a pointer to this U. The compiler will pick the most specialized template. The first three cout statements in main are easy to guess, but can you explain which statement foo4.bar(2.3, 5) will print and why?

More information on partial specialization:

Inheritance

Notice how templates also nicely work together with inheritance:

template <typename T, typename U>
class Base {
public:
    Base() {};
    virtual T getValueOne() = 0;
    virtual U getValueTwo() = 0;
};

template <typename T, typename U>
class Derived : public Base<T, U> {
public:
    Derived(T vOne, U vTwo) : valueOne{vOne}, valueTwo(vTwo) {};
    virtual T getValueOne() { return this->valueOne; };
    virtual U getValueTwo() { return this->valueTwo; };

    virtual ~Derived() {};
private:
    T valueOne;
    U valueTwo;
};

// full specialization!
template <>
class Derived<int, double> : public Base<int, double> {
public:
    Derived(int vOne, double vTwo) : valueOne{vOne}, valueTwo(vTwo) {};
    virtual int getValueOne() { return this->valueOne + 100; };
    virtual double getValueTwo() { return this->valueTwo + 100.0; };

    virtual ~Derived() {};
private:
    int valueOne;
    double valueTwo;
};

We even defined a fully specialized Derived class template for the types int and double! Executing the following gives the expected output:

#include <iostream>
#include <memory>

template<typename U, typename T> using base = std::unique_ptr<Base<U, T>>; // alias template
using base_d_d = std::unique_ptr<Base<double, double>>; // alias declaration

int main() {
    base<int, double> derived = std::make_unique<Derived<int, double>>(4, 3.0);
    std::cout << derived->getValueOne() << std::endl; // OUTPUTS: 104
    std::cout << derived->getValueTwo() << std::endl; // OUTPUTS: 103

    base_d_d derived2 = std::make_unique<Derived<double, double>>(4.1, 3.0);
    std::cout << derived2->getValueOne() << std::endl; // OUTPUTS: 4.1
    std::cout << derived2->getValueTwo() << std::endl; // OUTPUTS: 3

    return 0;
}

Member templates

So now we had function templates and class templates. Next to those, a class or class template can also have members that are templates: member templates. This can give you an extra degree of control/freedom on related types (which are represented by the class (template)). Look at the following very simple example of a member template of a class:

class Foo {
public:
    // ...
    template <typename T>
    void bar(int u, T v) { std::cout << "data = " << 5 << ", v = " << v << std::endl; }; // member template
private:
    // ...
};

Now let's make it a member template of a class template:

template <typename U>
class Foo {
public:
    // ...
    template <typename T>
    void bar(U u, T v) { std::cout << "data = " << 5 << ", v = " << v << std::endl;}
    // member template of a template class
private:
    // ...
};

The out-of-class definition of bar would look like this:

template <typename U>
template <typename T>
void Foo<U>::bar(U u, T v) {
    std::cout << "data = " << 5 << ", v = " << v << std::endl;
}

As stated here, you can also apply full specialization on member templates. Make a specialization work for bar. A limitation of a member (function) template is that it can not be virtual.

More information:

Template metaprogramming

The introduction of templates to the C++ syntax grammar itself defines a new Turing complete language inside C++. This allows to execute programs written as templates at compile time. This is known as template metaprogramming.

One of the most used examples of this is the calculation of a factorial with templates:

// General case with recursion
template <int n>
struct factorial {
    enum {
        value = n * factorial<n - 1>::value
    };
};

// Specialisation used as a stop condition for the recursion
template <>
struct factorial<0> {
    enum {
        value = 1
    };
};

How is it possible that we can define this? You've probably read by now that templates also allow non-type arguments (whose value can be determined at compile time), such as in this case an int value 0. These non-type arguments are actually used in a lot of other use cases, next to metaprogramming. One of these use cases we actually already ran into: std::get in combination with std::tuple in Session 6.

You can use the above class template to get 10! at compile-time:

int fact_10 = factorial<10>::value;

There's a variety of other applications and advanced techniques, for instance static polymorphism (CRTP: Curiously Recurring Template Pattern).

Some interesting reading material on the subject of template metaprogramming can be found in the following documents:

Further reading material on templates

Exercises

Data<T> Wrapper 1

Go to the assignment: https://classroom.github.com/a/HCtht7S-

Data<T> Wrapper 2

Go to the assignment: https://classroom.github.com/a/1Xz7Jit5