16.6. Template Specializations
It is not always possible to write a single template that is best suited for every possible template argument with which the template might be instantiated. In some cases, the general template definition is simply wrong for a type. The general definition might not compile or might do the wrong thing. At other times, we may be able to take advantage of some specific knowledge about a type to write a more efficient function than the one that is instantiated from the template. Our compare function and our Queue class are both good examples of the problem: Neither works correctly when used with C-style character strings. Let's look again at our compare function template: template <typename T> int compare(const T &v1, const T &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; } If we call this template definition on two const char* arguments, the function compares the pointer values. It will tell us the relative positions in memory of these two pointers but says nothing about the contents of the arrays to which the pointers point. To get be able to use compare with character strings, we would have to provide a specialized definition that knows how to compare C-style strings. The fact that these versions are specialized is transparent to users of these templates. Calls to a specialized function or use of a specialized class are indistinguishable from uses of a version instantiated from the general template. 16.6.1. Specializing a Function TemplateA template spacialization is a separate definition in which the actual type(s) or value(s) of one or more template parameter(s) is (are) specified. The form of a specialization is:
The following program defines a specialization of compare when the template parameter type is bound to const char*:
// special version of compare to handle C-style character strings
template <>
int compare<const char*>(const char* const &v1,
const char* const &v2)
{
return strcmp(v1, v2);
}
The declaration for the specialization must match that of the corresponding template. In this case, the template has one type parameter and two function parameters. The function parameters are const references to the type parameter. Here we are fixing the type parameter to const char*; our function parameters, therefore, are const references to a const char*. Now when we call compare, passing it two character pointers, the compiler will call our specialized version. It will call the generic version for any other argument types (including plain char*): const char *cp1 = "world", *cp2 = "hi"; int i1, i2; compare(cp1, cp2); // calls the specialization compare(i1, i2); // calls the generic version instantiated with int Declaring a Template SpecializationAs with any function, we can declare a function template specialization without defining it. A template specialization declaration looks like the definition but omits the function body:
// declaration of function template explicit specialization
template<>
int compare<const char*>(const char* const&,
const char* const&);
This declaration consists of an empty template parameter list (template<>) followed by the return type, the function name (optionally) followed by explicit template argument(s) specified inside a pair of angle brackets, and the function parameter list. A template specialization must always include the empty template parameter specifier, template<>, and it must include the function parameter list. If the template arguments can be inferred from the function parameter list, there is no need to explicitly specify the template arguments: // error: invalid specialization declarations // missing template<> int compare<const char*>(const char* const&, const char* const&); // error: function parameter list missing template<> int compare<const char*>; // ok: explicit template argument const char* deduced from parameter types template<> int compare(const char* const&, const char* const&); Function Overloading versus Template SpecializationsOmitting the empty template parameter list, template<>, on a specialization may have surprising effects. If the specialization syntax is missing, then the effect is to declare an overloaded nontemplate version of the function: // generic template definition template <class T> int compare(const T& t1, const T& t2) { /* ... */ } // OK: ordinary function declaration int compare(const char* const&, const char* const&); The definition of compare does not define a template specialization. Instead, it declares an ordinary function with a return type and a parameter list that could match those of a template instantiation. We'll look at the interaction of overloading and templates in more detail in the next section. For now, what's important to know is that when we define a nontemplate function, normal conversions are applied to the arguments. When we specialize a template, conversions are not applied to the argument types. In a call to a specialized version of a template, the argument type(s) in the call must match the specialized version function parameter type(s) exactly. If they don't, then the compiler will instantiate an instantiation for the argument(s) from the template definition. Duplicate Definitions Cannot Always Be DetectedIf a program consists of more than one file, the declaration for a template specialization must be visible in every file in which the specialization is used. A function template cannot be instantiated from the generic template definition in some files and be specialized for the same set of template arguments in other files.
Ordinary Scope Rules Apply to SpecializationsBefore we can declare or define a specialization, a declaration for the template that it specializes must be in scope. Similarly, a declaration for the specialization must be in scope before that version of the template is called: // define the general compare template template <class T> int compare(const T& t1, const T& t2) { /* ... */ } int main() { // uses the generic template definition int i = compare("hello", "world"); // ... } // invalid program: explicit specialization after call template<> int compare<const char*>(const char* const& s1, const char* const& s2) { /* ... */ } This program is in error because a call that would match the specialization is made before the specialization is declared. When the compiler sees a call, it must know to expect a specialization for this version. Otherwise, the compiler is allowed to instantiate the function from the template definition.
It is an error for a specialization to appear after a call to that instance of the template has been seen. 16.6.2. Specializing a Class TemplateOur Queue class has a problem similar to the one in compare when used with C-style strings. In this case, the problem is in the push function. That function copies the value it's given to create a new element in the Queue. By default, copying a C-style character string copies only the pointer, not the characters. Copying a pointer in this case has all the problems that shared pointers have in other contexts. The most serious is that if the pointer points to dynamic memory, it's possible for the user to delete the array to which the pointer points. Defining a Class SpecializationOne way to provide the right behavior for Queue's of C-style strings is to define a specialized version of the entire class for const char*: /* definition of specialization for const char* * this class forwards its work to Queue<string>; * the push function translates the const char* parameter to a string * the front functions return a string rather than a const char* */ template<> class Queue<const char*> { public: // no copy control: Synthesized versions work for this class // similarly, no need for explicit default constructor either void push(const char*); void pop() {real_queue.pop();} bool empty() const {return real_queue.empty();} // Note: return type does not match template parameter type std::string front() {return real_queue.front();} const std::string &front() const {return real_queue.front();} private: Queue<std::string> real_queue; // forward calls to real_queue }; This implementation gives Queue a single data element: a Queue of strings. The various members delegate their work to this memberfor example, pop is implemented by calling pop on real_queue. This version of the class does not define the copy-control members. Its only data element has a class type that does the right thing when copied, assigned, or destroyed; we can use the synthesized copy-control members. Our Queue class implements mostly, but not entirely, the same interface as the template version of Queue. The difference is that we return a string rather than a char* from the front members. We do so to avoid having to manage the character array that would be required if we wanted to return a pointer. It is worth noting that a specialization may define completely different members than the template itself. If a specialization fails to define a member from the template, that member may not be used on objects of the specilization type. The member definitions of the class template are not used to create the definitions for the members of an explicit specialization.
Class Specialization Definition
Our class defines only one member outside the class: void Queue<const char*>::push(const char* val) { return real_queue.push(val); } Although it does little obvious work, this function implicitly copies the character array to which val points. The copy is made in the call to real_queue.push, which creates a new string from the const char* argument. That argument uses the string constructor that takes a const char*. The string constructor copies the characters from the array pointed to by val into an unnamed string that will be stored in the element we push onto real_queue.
16.6.3. Specializing Members but Not the ClassIf we look a bit more deeply at our class, we can see that we can simplify our code: Rather than specializing the whole template, we can specialize just the push and pop members. We'll specialize push to copy the character array and pop to free the memory we used for that copy: template <> void Queue<const char*>::push(const char *const &val) { // allocate a new character array and copy characters from val char* new_item = new char[strlen(val) + 1]; strncpy(new_item, val, strlen(val) + 1); // store pointer to newly allocated and initialized element QueueItem<const char*> *pt = new QueueItem<const char*>(new_item); // put item onto existing queue if (empty()) head = tail = pt; // queue has only one element else { tail->next = pt; // add new element to end of queue tail = pt; } } template <> void Queue<const char*>::pop() { // remember head so we can delete it QueueItem<const char*> *p = head; delete head->item; // delete the array allocated in push head = head->next; // head now points to next element delete p; // delete old head element } Now, the class type Queue<const char*> will be instantiated from the generic class template definition, with the exception of the push and pop functions. When we call push or pop on a Queue<const char*>, then the specialized version will be called. When we use any other member, the generic one will be instantiated for const char* from the class template. Specialization DeclarationsMember specializations are declared just as any other function template specialization. They must start with an empty template parameter list: // push and pop specialized for const char* template <> void Queue<const char*>::push(const char* const &); template <> void Queue<const char*>::pop(); These declarations should be placed in the Queue header file.
16.6.4. Class-Template Partial SpecializationsIf a class template has more than one template parameter, we might want to specialize some but not all of the template parameters. We can do so using a class template partial specialization: template <class T1, class T2> class some_template { // ... }; // partial specialization: fixes T2 as int and allows T1 to vary template <class T1> class some_template<T1, int> { // ... }; A class template partial specialization is itself a template. The definition of a partial specialization looks like a template definition. Such a definition begins with the keyword template followed by a template parameter list enclosed by angle brackets (<>). The parameter list of a partial specialization is a subset of the parameter list of the corresponding class template definition. The partial specialization for some_template has only one template type parameter named T1. The second template argument for T2 is known to be int. The template parameter list for the partial specialization only lists the parameters for which the template arguments are still unknown. Using a Class-Template Partial SpecializationThe partial specialization has the same name as the class template to which it correspondsnamely, some_template. The name of the class template must be followed by a template argument list. In the previous example, the template argument list is <T1,int>. Because the argument value for the first template parameter is unknown, the argument list uses the name of the template parameter T1 as a placeholder. The other argument is the type int, for which the template is partially specialized. As with any other class template, a partial specialization is instantiated implicitly when used in a program: some_template<int, string> foo; // uses template some_template<string, int> bar; // uses partial specialization Notice that the type of the second variable, some_template parameterized by string and int, could be instantiated from the generic class template definition as well as from the partial specialization. Why is it that the partial specialization is chosen to instantiate the template? When a parital specialization is declared, the compiler chooses the template definition that is the most specialized for the instantiation. When no partial specialization can be used, the generic template definition is used. The instantiated type of foo does not match the partial specialization provided. Thus, the type of foo must be instantiated from the general class template, binding int to T1 and string to T2. The partial specialization is only used to instantiate some_template types with a second type of int. The definition of a partial specialization is completely disjointed from the definition of the generic template. The partial specialization may have a completely different set of members from the generic class template. The generic definitions for the members of a class template are never used to instantiate the members of the class template partial specialization. ![]() |