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:
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]
|
Ru-Brd |