Full class template specialization and full member specialization

Templates are a very powerful feature of C++: they let us parameterize a class, function or variable with some generic types or values (called template parameters), so that by specifying those types or values (by providing so-called template arguments), we can create (or instantiate) a specific version of that class, function or variable.

When  a template is instantiated, the compiler generates code for a new version of that class, function or variable by substituting the generic parameters with the template arguments (explicitly specified by the user or implicitly deduced by the compiler). This process is called implicit instantiation or automatic instantiation, since it’s the compiler that automatically generates the code for the instantiated template.

In most cases this is fine, and we can let the compiler perform its magic. Sometimes though, we need to assume control over the instantiation process, to fine-tune the generation of the template instance. This is what template specializations are for.

(Note: In this post I’m going to cover explicit (full) class template specialization, and full member specialization, but keep in mind that function templates and variable templates can also be fully specialized. Also remember that alias templates are the only templates that can’t be specialized, so I leave them out of this discussion as well.)

Explicit class template specialization

Take a class template, parameterized by a single template type parameter T:

The generic, unspecialized template is called the primary template. This class template contains three member:  a member function, a static data member and a nested class (which, in turn, contains other members). Members of class templates that are defined out of the class template definition need a “template <typename T>” (called template parameterization clause) prefix  before each definition (since they belong to a class template, they depends on template parameters). Beacuse the compiler needs to see a template definition in order to generate an instantiation for it, definitions of out of class (non-inline) class templates members are put in the same header as the class template definition.

To create a variable from a class template we need to specify the template arguments inside angle brackets: a class template doesn’t specify a type but, well…. a template, which is kind of a blueprint for creating concrete types. A real type is made of a template name plus its arguments, explicit or deduced (a template-id). If we create a variable of type, say, AClass<double>, the compiler automatically generates an instance of that class template with “double” substituted for the generic type parameter T:

This is fine, but what if we want to provide a completely different implementation for this class template when it is instantiated with a specific type, for example int? Why would we want to do that, I can hear you ask. Well, for example we’d like to implement some optimizations for specific types, that wouldn’t suit a general type, or maybe the generic implementation wouldn’t even compile for a certain template arguments (what if the generic implementation had a type member like typename T::iterator ?).  We can do so by providing an explicit (full) class template specialization:

Full template specializations must always follow the primary template. They are introduced by the “template <>” prefix and the name of the class must be followed by the template arguments in angle brackets, to specify that we’re specializing the class template for those specific arguments.

An important thing to notice about full class template specializations is that the specialization is completely independent of the original class template: in this example the members f the specialization are totally different form the those of the primary class template. Another thing to keep in mind is that the full specialization is not a template anymore, it’s more like an ordinary class definition, so out of class definitions of class members must not be preceded by the “template <>”  prefix. Furthermore, definitions of member functions and static data members of the specialization must be placed inside a cpp file (to avoid breaking the ODR – one definition rule) while out of class definitions of nested classes of the specialization should be placed in the same header file (see above):

Now, if we instantiate the template with int, we override the automatic instantiation process of the compiler, and the provided specialization is used instead:

Full member specialization

Not only can entire class templates be fully specialized, we can also specialize single members of class templates (member functions, static data members or nested classes), leaving the rest of the class as the original template. As usual, specialized members must be defined outside their classes, but now a “template <>” prefix must be used before the definition, to indicate that we’re specializing them; if the member is nested inside more class templates, we need a “template <>” prefix for each enclosing class template (in this example members to be specialized are inside a class template so just one “template <>” prefix is needed). Since those definitions are not templates anymore (they’ve been fully specialized), we can’t put them inside the header file with the class template definition (one definition rule, remember?), so we put them in the cpp file:

The compiler needs to know that those members have been specialized in order to use the provided specialization and inhibit the automatic instantiation process in each source file in which a use of the template appears. So we can’t put them in the header file, and we need to tell the compiler to use our specialization. To solve this conundrum, we put inside the header file what are called non-defining declarations:

Normally it’s forbidden to put out of class declarations that don’t define a member. But in the case of full member specialization this is fine and allowed. This tells the compiler that a specialization for those member has been provided, so the linker will look for one when building the program.

We could argue that the non defining declaration for the static data member resembles a a definition for a default initialized float: this is true, but in these cases those constructs are always interpreted ad non defining declarations, so we’d need to sepcify a default value with a brace-initializer {}, or copy-initialize the variable with an explicit default value.

As in the full class template specialization case, this doesn’t apply to nested class members. We can put the definition for the specialized member class inside the header file:

Definitions for  members of the specialized member class must be placed inside the cpp file as usual (and they don’t need the “template <>” prefix):

Now, if we instantiate the class template with float, the specializations for the class members are used:

We can even specialize only a member inside a nested class, but remember to put a “template <>” prefix for each enclosing class template:

So if we call the nested class’ member function with short as a template argument, this version is used:

Keep in mind that in general, when we fully specialize a member, we need to prefix the out of class definition with as many “template <>” prefixes as are the enclosing class templates. Also it’s important to note that once a member has been specialized for some template arguments, it’s invalid to provide a full class template specialization for those same arguments.

Full specialization of member templates

Templates can be defined at global or namespace scope, and they’re called class templates, function templates and variable templates (we don’t consider alias templates, since they can’t be specialized). When they’re defined at class scope they become  nested class templates, member function templates and static member variable templates (and member alias templates, but as said, we leave them out). They’re collectively known as member templates.

As usual members can be defined outside of the class definition, specifying the template parameter list for the enclosing class template as well as the template parameter list for the member template:

Now, our class template members are templates themselves, so we can specialize them by specifying the template arguments for the enclosing class template(s):

We can also provide full specializations for the member template, by specifying template arguments for them in addition to specifying template arguments for the enclosing class template(s), but only for a specific instance of the enclosing class template(s); as usual, since in this case the specializations are no more templates, we need to put them in the cpp file, and provide non defining declarations inside the header file (if the member template is a class, it becomes a full class template specialization):

We can now instantiate some templates in our application and test things out:

In conclusion, if we need to fully specialize a member of a class template we just need to specify it out of the class, put a “template <>” prefix for each enclosing class template that we’re specializing, and if the member is a member template (and we’re specializing it), we just add another “template <>” prefix, to specify that we’re specializing it. For example, if we want to specialize the member function void Function() inside the class template Inner inside the primary class template AClass we can do as follows:

 

 

Leave a Reply

Your email address will not be published.