Welcome back to the wonderful world of templates.
So far, we have looked at what are known as base templates. In this article we’re going to look at one of the more confusing aspects of templates – specialisation. The choice of the word specialisation is unfortunate, as many confuse it with inheritance and sub-typing; in this case specialised means “more specific”.
Template specialisation comes in two forms:
- Explict specialisation, where a unique version of the template is defined for a specific type
- Partial specialisation, where a template is defined that acts on a qualified range of types (for example, pointers-to-type)
To add to the confusion template functions and template classes behave in different ways:
- Template functions may be explicitly specialised, but not partially specialised. Template functions may be overloaded, though.
- Template classes cannot be overloaded; but they can be both explicitly- and partially-specialised.
To help overcome some of this confusion, think how non-template functions and classes work:
- Functions can be overloaded, but not inherited
- Classes may be inherited, but not overloaded
Template functions and classes attempt to follow similar semantics. But let’s have a look at them in more detail.
Overloading template functions
Back in the first article in this series we said that templates can be overloaded with non-template versions.
The compiler will always favour the non-template version of the function.
It is also possible to overload the template function with another template function.
With no non-template overload, the compiler chooses the template function that is most specialised – that is, the template function that best fits the parameters it deduces from the call.
In the first call, the compiler deduces the parameter type is int* (the declared type of p1 and p2) and so instantiates the template that matches best – T* min(T* a, T* b)
In the second call, the compiler deduces the type of T as int, and so instantiates the less-specialised template – T min(T a, T b).
Explicitly specialised template functions
It is also possible to explicitly specialise a template function. An explicitly specialised template function is when the function is declared for a specific type.
The above code gives ostensibly the same results as the non-template overload. However, because of the (arcane) rules the compiler uses to determine which template to instantiate you may not always get the result you anticipated.
I recommend that you ALWAYS prefer non-template overloads to template function specialisation.
(The reasons are beyond the scope of this article; and Herb Sutter has written an excellent description of the problem here.)
Template functions may be overloaded for different signatures and explicit specialisations which gives the illusion of function partial specialisation. However, because of subtleties in the way the compiler decides which template function to instantiate, overloading template functions with other template functions is best avoided.(The reasons are beyond the scope of this article; and Herb Sutter has written an excellent description of the problem here.)
Explicitly specialised template classes
Let’s switch our attention to template classes. As I said in the introduction, with classes we can provide both explicitly specialised and partially-specialised versions of the template class.
In this example we have a class MinFinder, with a single method, get(). We can create instances of the template class for different types but in the case of strings the behaviour we get is not (necessarily) what we might want.
We can explicitly specialise the MinFinder class, creating a version that works specifically for const char* objects.
Notice in this case the compiler is not doing any type deduction – we are explicitly telling the compiler which template class to instantiate. This is a bit clumsy; we’ll look into that shortly.
Partially specialised template classes
In the above example we have specialised for a particular type (const char*). However, we may want different behaviour for all pointer types – for example, comparing the referents of the pointers, rather than the pointers themselves. Unlike template functions we can’t overload template classes. It would be impracticable to provide explicit specialisations for every pointer type, so C++ allows us to provide a partial specialisation.
Now we have defined:
- A base template, which works for everything; except…
- A partial specialisation, which will be favoured for all pointers-to-type; except…
- An explicit specialisation, which works specifically for const char* types.
The compiler will choose the appropriate template to instantiate based on the type of the template parameter it is instantiated with. Partial specialisations may be created for:
- References (r-value and l-value)
- const objects
- …and (valid) combinations of the above
Note that, if an explicit specialisation exists the compiler will prefer it to the partial specialisation. The compiler will always prefer the ‘most specialised’ (read: ‘most specific’) template. This extends for non-template classes – which, like template functions, will always be preferred to the equivalent template version
Hiding the template specialisations
At this point we have the ability to provide unique behaviours through partial- and explicitly-specialised template classes; but we must explicitly create the class we want to use.
To improve the usability of our code we need to combine template functions and template classes:
- Use the compiler’s ability to deduce template types for template functions
- Use the ability to partially specialise template classes to get different beahviours for different (groups of) types.
The mechanism is to wrap the instantiation of a template class inside a template function:
The compiler deduces the template parameters from the function call; this is then used to create the appropriate (partially) specialised class, which does the work for us.
Template specialisation is a powerful way of building expressive library code, by allowing us to define different beahviours for different ‘categories’ of types. The real strength of this is allowing us to extend libraries after-the-fact, for types that may have not been considered during the library’s conception.
Template functions may be overloaded for different signatures and explicit specialisations which gives the illusion of function partial specialisation. However, because of subtleties in the way the compiler decides which template function to instantiate, overloading template functions with other template functions is best avoided.
Template classes can be both explicitly and partially specialised, but must be explicitly instantiated. This presents a possible limitation for library code, but can be circumvented by combining template partial specialisations with template function type deduction.
In the final article of this series we’ll have a look at the problem of communicating type information between different templates, using trait template classes.