Ru-Brd |
Full template specialization is often useful, but sometimes it is natural to want to specialize a class template for a family of template arguments rather than just one specific set of template arguments. For example, let's assume we have a class template implementing a linked list: template<typename T> class List { // (1) public: void append(T const&); inline size_t length() const; }; A large project making use of this template may instantiate its members for many types. For member functions that are not expanded inline (say, List<T>::append() ), this may cause noticeable growth in the object code. However, we may know that from a low-level point of view, the code for List<int*>::append() and List<void*>::append() is the same. In other words, we'd like to specify that all List s of pointers share an implementation. Although this cannot be expressed in C++, we can achieve something quite close by specifying that all List s of pointers should be instantiated from a different template definition: template<typename T> class List<T*> { // (2) private: List<void*> impl; public: void append(T* p) { impl.append(p); } size_t length() const { return impl.length(); } }; In this context, the original template at point (1) is called the primary template , and the latter definition is called a partial specialization (because the template arguments for which this template definition must be used have been only partially specified). The syntax that characterizes a partial specialization is the combination of a template parameter list declaration ( template<...> ) and a set of explicitly specified template arguments on the name of the class template ( <T*> in our example). Our code contains a problem because List<void*> recursively contains a member of that same List<void*> type. To break the cycle, we can precede the previous partial specialization with a full specialization: template<> class List<void*> { // (3) void append (void* p); inline size_t length() const; }; This works because matching full specializations are preferred over partial specializations. As a result, all member functions of List s of pointers are forwarded (through easily inlineable functions) to the implementation of List<void*> . This is an effective way to combat so-called code bloat (of which C++ templates are often accused). There exists a number of limitations on the parameter and argument lists of partial specialization declarations. Some of them are as follows :
An example illustrates these limitations: template<typename T, int I = 3> class S; // primary template template<typename T> class S<int, T>; // ERROR: parameter kind mismatch template<typename T = int> class S<T, 10>; // ERROR: no default arguments template<int I> class S<int, I*2>; // ERROR: no nontype expressions template<typename U, int K> class S<U, K>; // ERROR: no significant difference // from primary template Every partial specialization ”like every full specialization ”is associated with the primary template. When a template is used, the primary template is always the one that is looked up, but then the arguments are also matched against those of the associated specializations to determine which template implementation is picked. If multiple matching specializations are found, the "most specialized" one (in the sense defined for overloaded function templates) is selected; if none can be called "most specialized," the program contains an ambiguity error. Finally, we should point out that it is entirely possible for a class template partial specialization to have more parameters than the primary template. Consider our generic template List (declared at point (1)) again. We have already discussed how to optimize the list-of-pointers case, but we may want to do the same with certain pointer-to-member types. The following code achieves this for pointer-to-member-pointers: template<typename C> class List<void* C::*> { // (4) public: // partial specialization for any pointer-to- void* member // every other pointer-to-member-pointer type will use this typedef void* C::*ElementType; void append(ElementType pm); inline size_t length() const; }; template<typename T, typename C> class List<T* C::*> { // (5) private: List<void* C::*> impl; public: // partial specialization for any pointer-to-member-pointer type // except pointer-to- void* member which is handled earlier; // note that this partial specialization has two template parameters, // whereas the primary template only has one parameter typedef T* C::*ElementType; void append(ElementType pm) { impl.append((void* C::*)pm); } inline size_t length() const { return impl.length(); } }; In addition to our observation regarding the number of template parameters, note that the common implementation defined at (4) to which all others are forwarded (by the declaration at point (5)) is itself a partial specialization (for the simple pointer case it is a full specialization). However, it is clear that the specialization at point (4) is more specialized than that at point (5); thus no ambiguity should occur. |
Ru-Brd |