5.4 Template Template Parameters

Ru-Brd

It can be useful to allow a template parameter itself to be a class template. Again, our stack class template can be used as an example.

To use a different internal container for stacks, the application programmer has to specify the element type twice. Thus, to specify the type of the internal container, you have to pass the type of the container and the type of its elements again:

 Stack<int,std::vector<int> > vStack;  // integer stack that uses a vector  

Using template template parameters allows you to declare the Stack class template by specifying the type of the container without respecifying the type of its elements:

 stack<int,std::vector> vStack;  // integer stack that uses a vector  

To do this you must specify the second template parameter as a template template parameter. In principle, this looks as follows [2] :

[2] There is a problem with this version that we explain in a minute. However, this problem affects only the default value std::deque . Thus, we can illustrate the general features of template template parameters with this example.

  // basics/stack7decl.hpp  template <typename T,            template <typename ELEM> class CONT = std::deque >  class Stack {    private:      CONT<T> elems;  // elements  public:      void push(T const&);  // push element  void pop();  // pop element  T top() const;  // return top element  bool empty() const {  // return whether the stack is empty  return elems.empty();      }  }; 

The difference is that the second template parameter is declared as being a class template:

 template <typename ELEM> class CONT 

The default value has changed from std::deque<T> to std::deque . This parameter has to be a class template, which is instantiated for the type that is passed as the first template parameter:

 CONT<T> elems; 

This use of the first template parameter for the instantiation of the second template parameter is particular to this example. In general, you can instantiate a template template parameter with any type inside a class template.

As usual, instead of typename you could use the keyword class for template parameters. However, CONT is used to define a class and must be declared by using the keyword class . Thus, the following is fine:

 template <typename T,            template <class ELEM> class CONT = std::deque>  // OK  class Stack {   }; 

but the following is not:

 template <typename T,            template <typename ELEM> typename CONT = std::deque>  class Stack {  // ERROR    }; 

Because the template parameter of the template template parameter is not used, you can omit its name :

 template <typename T,            template <typename> class CONT = std::deque >  class Stack {   }; 

Member functions must be modified accordingly . Thus, you have to specify the second template parameter as the template template parameter. The same applies to the implementation of the member function. The push() member function, for example, is implemented as follows:

 template <typename T, template <typename> class CONT>  void Stack<T,CONT>::push (T const& elem)  {      elems.push_back(elem);  // append copy of passed  elem  } 

Template template parameters for function templates are not allowed.

Template Template Argument Matching

If you try to use the new version of Stack , you get an error message saying that the default value std::deque is not compatible with the template template parameter CONT . The problem is that a template template argument must be a template with parameters that exactly match the parameters of the template template parameter it substitutes. Default template arguments of template template arguments are not considered , so that a match cannot be achieved by leaving out arguments that have default values.

The problem in this example is that the std::deque template of the standard library has more than one parameter: The second parameter (which describes a so-called allocator ) has a default value, but this is not considered when matching std::deque to the CONT parameter.

There is a workaround, however. We can rewrite the class declaration so that the CONT parameter expects containers with two template parameters:

 template <typename T,            template <typename ELEM,                      typename ALLOC = std::allocator<ELEM> >                      class CONT = std::deque>  class Stack {    private:      CONT<T> elems;  // elements    }; 

Again, you can omit ALLOC because it is not used.

The final version of our Stack template (including member templates for assignments of stacks of different element types) now looks as follows:

  // basics/stack8.hpp  #ifndef STACK_HPP  #define STACK_HPP  #include <deque>  #include <stdexcept>  #include <allocator>  template <typename T,            template <typename ELEM,                      typename = std::allocator<ELEM> >                      class CONT = std::deque>  class Stack {    private:      CONT<T> elems;  // elements  public:      void push(T const&);  // push element  void pop();  // pop element  T top() const;  // return top element  bool empty() const {  // return whether the stack is empty  return elems.empty();      }  // assign stack of elements of type  T2      template<typename T2,               template<typename ELEM2,                        typename = std::allocator<ELEM2>                        >class CONT2>      Stack<T,CONT>& operator= (Stack<T2,CONT2> const&);  };  template <typename T, template <typename,typename> class CONT>  void Stack<T,CONT>::push (T const& elem)  {      elems.push_back(elem);  // append copy of passed  elem  }  template<typename T, template <typename,typename> class CONT>  void Stack<T,CONT>::pop ()  {      if (elems.empty()) {          throw std::out_of_range("Stack<>::pop(): empty stack");      }      elems.pop_back();  // remove last element  }  template <typename T, template <typename,typename> class CONT>  T Stack<T,CONT>::top () const  {      if (elems.empty()) {          throw std::out_of_range("Stack<>::top(): empty stack");      }      return elems.back();  // return copy of last element  }  template <typename T, template <typename,typename> class CONT>   template <typename T2, template <typename,typename> class CONT2>  Stack<T,CONT>&  Stack<T,CONT>::operator= (Stack<T2,CONT2> const& op2)  {      if ((void*)this == (void*)&op2) {  // assignment to itself?  return *this;      }      Stack<T2> tmp(op2);  // create a copy of the assigned stack  elems.clear();  // remove existing elements  while (!tmp.empty()) {  // copy all elements  elems.push_front(tmp.top());          tmp.pop();      }      return *this;  }  #endif  // STACK_HPP  

The following program uses all features of this final version:

  // basics/stack8test.cpp  #include <iostream>  #include <string>  #include <cstdlib>  #include <vector>  #include "stack8.hpp"  int main()  {      try {          Stack<int> intStack;  // stack of  int  s  Stack<float> floatStack;  // stack of  float  s   // manipulate  int  stack  intStack.push(42);          intStack.push(7);  // manipulate  float  stack  floatStack.push(7.7);  // assign stacks of different type  floatStack = intStack;  // print  float  stack  std::cout << floatStack.top() << std::endl;          floatStack.pop();          std::cout << floatStack.top() << std::endl;          floatStack.pop();          std::cout << floatStack.top() << std::endl;          floatStack.pop();      }      catch (std::exception const& ex) {          std::cerr << "Exception: " << ex.what() << std::endl;      }  // stack for  int  s using a vector as an internal container  Stack<int,std::vector> vStack;   vStack.push(42);      vStack.push(7);      std::cout << vStack.top() << std::endl;      vStack.pop();  } 

The program has the following output:

 7  42  Exception: Stack<>::top(): empty stack  7 

Note that template template parameters are one of the most recent features required for compilers to conform to the standard. Thus, this program is a good evaluation of the conformity of your compiler regarding template features.

For further discussions and examples of template template parameters, see Section 8.2.3 on page 102 and Section 15.1.6 on page 259.

Ru-Brd


C++ Templates
C++ Templates: The Complete Guide
ISBN: 0201734842
EAN: 2147483647
Year: 2002
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net