Saturday, June 2, 2012

Template Template Parameter in C++


Templates are a feature offered by C++  which facilitates generic programming and provide developers with the ability to parameterize the ‘type’ of argument. For this blog post I am assuming that the reader has some familiarity with generic programming and templates. Template template parameters are one aspect of template offerings by C++ that often gets overlooked. Though I was aware of this feature in C++ but I did not actually use it until recently when I happen to study it more meticulously.

The C++ Standard Template Library (STL) is one of the best examples of usage of templates with generic containers and algorithms. It provides three sequence containers namely vector, deque and list. Each of these containers is a template class. The vector class looks like following:

template <
         typename Type,
         typename Allocator = allocator<Type>
>
class vector

We can instantiate vector class with built-in types as template argument for e.g.

vector<int> vec1;
vector<string> vec2;

We can also use instantiated class templates as template argument for e.g.

vector<list<int>> vec3;
vector<vector<char>> vec4;

In the above example list<int> and vector<char> are instantiated class templates and not the class template itself. C++ does allow the template parameters to be class templates themselves. However, we need to tell the compiler that the template parameter is a class template. Let us take a look at an example of template template parameter:

template<typename T>
class my_array
{
       T* m_data;
       int m_size;

public:
       void push_back(T value);
       void pop_back();
       T* begin();
       T* end();
};

// class with template template parameter
template<typename T, template<typename U> class seq>
class container
{
private:
       seq<T> m_seq;

public:
       void append(const T& value) { m_seq.push_back(); }
       T* begin() { return m_seq.begin(); }
       T* end() { return m_seq.end(); }
};

container<int, my_array> cont;

In the above example, ‘seq’ is a template template parameter. There are couple of point to be noted here: 
  • The template template parameter, ‘seq’ in above example, must be introduced with the keyword class. The keyword typename is not allowed. This is due to the fact that typename indicate a simple type only whereas a template template parameter must be a template class and not a simple type.
  • The identifier ‘U’ in the declaration of template template parameter ‘seq’ is only valid inside its own declaration and cannot be used inside the scope of class ‘container’.

We can also write the class ‘container’ in above example in following manner and achieve exactly same functionality:

template< typename T, typename seq>
class container
{
       seq m_seq;

public:
       void append(const T& value) { m_seq.push_back(); }
       T* begin() { return m_seq.begin(); }
       T* end() { return m_seq.end(); }
};

container<int, my_array<int>> cont;

Now you might be having a question in your mind that if we can achieve the same effect using instantiated class templates, what is the need for template template parameter or what additional advantage it can provide? Templates enable us to work with generic types. In other words, it enables us to abstract the types of the application's local data in the implementation. Template template parameters add to that by enabling one to introduce an additional level of abstraction. To understand this, let us work with another example.

Suppose we want to implement a library which performs numeric operations on very long integers which cannot be represented by built-in types on operating system. The core of the problem is how to represent such long integers. There are many options that we can consider using for e.g. array, singly linked list, doubly linked list, dynamically allocated memory etc. Given that at the beginning of project we do not have an exhaustive list of all algorithms that we will be implementing, the possible context under which algorithms may be used and the fact that choosing a particular data structure/container for representing the long integer will influence the way we implement the algorithm; it is hard to choose a perfect container for all possible scenarios. A wiser approach would be to leave this decision open and parameterize the long integer class by the ‘container’ which holds the digits. We keep the interface minimal where a long integer is a sequence of digits and each digit is accessible by iterator.

Following is a sample implementation of long integer class using simple template parameters. The class is instantiated by using the ‘instantiated class template’ of the container as argument for template parameter.

template<typename cont_type = vector<int>>
class integer_long {
public:
  typedef typename cont_type::value_type value_type;
  typedef typename cont_type::iterator iterator;
  iterator begin() { return cont.begin(); }
  iterator end() { return cont.end(); }
  void push_back(value_type  v) { cont.push_back(v); }

private:
  cont_type cont;
};

template<typename cont_type = vector<int>>
integer_long<cont_type> Add(
integer_long<cont_type> num_1,
integer_long<cont_type> num_2
);

// using default value for template argument
integer_long<> n_1, n_2, n_3;
n_3 = Add(n_1, n_2);

// using instantiated class template
// (deque<int>) as template argument
integer_long <deque<int>> n_4, n_5, n_6;
n_6 = Add(n_4, n_5);

A sample implementation of the long integer class using template template parameter would look like following. The class is instantiated using the real class template of the container as argument for template parameter.

template<template<typename T, typename A> class cont_type = vector>
class integer_long {
public:
  typedef int digit_type;
  typedef allocator alloc_type
  typedef typename  cont_type<digit_typealloc_type>::iterator iterator;
  iterator begin() { return cont.begin(); }
  iterator end() { return cont.end(); }
  void push_back(digit_type v) { cont.push_back(v); }

private:
  cont_type<digit_type, alloc_type> cont;
};

template<
       template<typename T, typename A> cont_type = vector,
       template<typename U> alloc_type = allocator
> 
integer_long<cont_type, alloc_type> Add(
       integer_long<cont_type, alloc_type> num_1,
       integer_long<cont_type, alloc_type> num_2
);

// using default value for template argument
integer_long<> n_1, n_2, n_3;
n_3 = Add(n_1, n_2);

// using real class template (deque and allocator)
// not the instantiation as template argument
integer_long<deque, allocator> n_4, n_5, n_6;
n_6 = Add(n_4, n_5);

Let us now compare and contrast the two techniques. If the user chooses not to provide any template arguments and go ahead with default arguments, both the approaches are same from usage perspective and can have same underlying implementation ( i.e. sequence container in above example).

Each of the sequence container provided by STL (vector, deque and list) has specific characteristics which are suitable to different context and requirements. Hence we would want to allow the user to be able to specify the sequence container of his/her choice.  In case of simple template parameter technique, the user can pass an instantiated class template (of sequence container of his/her choice) as template argument. Along with type of sequence container, user also provides the internal type (which is the type of a digit) and memory allocator type, which are used to instantiate the sequence container. However, more often than not, we might want to treat these as implementation details. But with simple template parameter technique we cannot achieve this. This is where template template parameter comes in handy.

If you look at the sample code above using template template parameter technique, the type of a digit (i.e. internal type of sequence container) and type of memory allocator, are treated as implementation details. We only make them available by defining public types called ‘digit type’ and ‘alloc_type’ in our long integer class. The user still has the option to specify the type of sequence container of his choice while instantiating the long integer class.

Also note that template template parameter approach provides the ability to create multiple, different instantiations of sequence container inside the class template body using the template template argument. This is not possible with simple template parameter technique. The additional level of abstraction and flexibility offered by the template template parameter technique can be used by library designers and developers to provide specialized versions of certain algorithms which make use of the specific features of the underlying container. It gives the users enough flexibility to exercise their choice depending on context of use meanwhile insulating them from unnecessary implementation specific details.

I hope after reading this post you now have a better insight and understanding of template template parameter feature offered by C++.