5.3 Member Templates

Ru-Brd

Class members can also be templates. This is possible for both nested classes and member functions. The application and advantage of this ability can again be demonstrated with the Stack<> class template. Normally you can assign stacks to each other only when they have the same type, which implies that the elements have the same type. However, you can't assign a stack with elements of any other type, even if there is an implicit type conversion for the element types defined:

 Stack<int> intStack1, intStack2;  // stacks for  int  s  Stack<float> floatStack;  // stack for  float  s    intStack1 = intStack2;  // OK: stacks have same type  floatStack = intStack1;  // ERROR: stacks have different types  

The default assignment operator requires that both sides of the assignment operator have the same type, which is not the case if stacks have different element types.

By defining an assignment operator as a template, however, you can enable the assignment of stacks with elements for which an appropriate type conversion is defined. To do this you have to declare Stack<> as follows :

  // basics/stack5decl.hpp  template <typename T>  class Stack {    private:      std::deque<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>      Stack<T>& operator= (Stack<T2> const&);  }; 

The following two changes have been made:

  1. We added a declaration of an assignment operator for stacks of elements of another type T2 .

  2. The stack now uses a deque as an internal container for the elements. Again, this is a consequence of the implementation of the new assignment operator.

The implementation of the new assignment operator looks like this:

  // basics/stack5assign.hpp  template <typename T>   template <typename T2>  Stack<T>& Stack<T>::operator= (Stack<T2> 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;  } 

First let's look at the syntax to define a member template. Inside the template with template parameter T , an inner template with template parameter T2 is defined:

 template <typename T>   template <typename T2>   

Inside the member function you may expect simply to access all necessary data for the assigned stack op2 . However, this stack has a different type (if you instantiate a class template for two different types, you get two different types), so you are restricted to using the public interface. It follows that the only way to access the elements is by calling top() . However, each element has to become a top element, then. Thus, a copy of op2 must first be made, so that the elements are taken from that copy by calling pop() . Because top() returns the last element pushed onto the stack, we have to use a container that supports the insertion of elements at the other end of the collection. For this reason, we use a deque, which provides push_front() to put an element on the other side of the collection.

Having this member template, you can now assign a stack of int s to a stack of float s:

 Stack<int> intStack;  // stack for  int  s  Stack<float> floatStack;  // stack for  float  s    floatStack = intStack;  // OK: stacks have different types,   //     but  int  converts to  float 

Of course, this assignment does not change the type of the stack and its elements. After the assignment, the elements of the floatStack are still floats and therefore pop() still returns a float .

It may appear that this function would disable type checking such that you could assign a stack with elements of any type, but this is not the case. The necessary type checking occurs when the element of the (copy of the) source stack is moved to the destination stack:

 elems.push_front(tmp.top()); 

If, for example, a stack of strings gets assigned to a stack of float s, the compilation of this line results in an error message stating that the string returned by tmp.top() cannot be passed as an argument to elems.push_front() (the message varies depending on the compiler, but this is the gist of what is meant ):

 Stack<std::string> stringStack;  // stack of  int  s  Stack<float>       floatStack;  // stack of  float  s    floatStack = stringStack;  // ERROR:  std::string  doesn't convert to  float 

Note that a template assignment operator doesn't replace the default assignment operator. For assignments of stacks of the same type, the default assignment operator is still called.

Again, you could change the implementation to parameterize the internal container type:

  // basics/stack6decl.hpp  template <typename T, typename CONT = std::deque<T> >  class Stack {    private:      CONT 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, typename CONT2>      Stack<T,CONT>& operator= (Stack<T2,CONT2> const&);  }; 

Then the template assignment operator is implemented like this:

  // basics/stack6assign.hpp  template <typename T, typename CONT>   template <typename T2, typename 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;  } 

Remember, for class templates, only those member functions that are called are instantiated . Thus, if you avoid assigning a stack with elements of a different type, you could even use a vector as an internal container:

  // stack for  int  s using a vector as an internal container  Stack<int,std::vector<int> > vStack;   vStack.push(42);  vStack.push(7);  std::cout << vStack.pop() << std::endl; 

Because the assignment operator template isn't necessary, no error message of a missing member function push_front() occurs and the program is fine.

For the complete implementation of the last example, see all the files with a name that starts with " stack6 " in the subdirectory basics . [1]

[1] Don't be surprised if your compiler reports errors with these sample files. In the samples, we use almost every important template feature. Thus, you need a compiler that conforms closely to the standard.

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