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:

// specializion.hpp

#include <iostream>

#define PRINT(msg)          std::cout << (msg)                    // some utility macro
#define PRINTLN(msg)        PRINT(msg) << std::endl;


/**** primary template ****/

template <typename T>
class AClass
{
public:
    T MemberFunction(T const &);      // member function
    static T StaticDataMember;        // static data member
    struct Inside;                    // nested class
};

// class template member definitions 

template <typename T>
T AClass<T>::MemberFunction(const T &t)
{
    PRINTLN("class template member function");
}

template <typename T>
T AClass<T>::StaticDataMember = T();   // assume T default constructible

template <typename T>
struct AClass<T>::Inside
{
    void Function();
};

template <typename T>
void AClass<T>::Inside::Function()
{
    PRINTLN("class template nested class member function");
}

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:

// main.cpp

#include "specialization.hpp"

int main(int argc, char **argv)
{
    AClass<double> ad;
    ad.MemberFunction(1.0);          // prints "class template member function"

    PRINTLN(ad.StaticDataMember);    // prints 0

    AClass<double>::Inside adi;    
    adi.Function();                  // prints "class template nested class member function"
    
    // ....
}

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:

// specialization.hpp

/**** full class template specialization (member definitions in cpp file) ****/

template <>             // empty angle brackets
class AClass<int>       // template name with arguments in angle brackets
{
public:
    void MemberFunction();               // a different member function
    static float StaticDataMember;       // a different static data member
    struct Inner;                        // a different struct (another name)
};

// nested class of specialization inside header file
// no template <> prefix

struct AClass<int>::Inner
{
    double MemberFunction(int);
};

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):

// specialization.cpp

#include "specialization.hpp"

// definitions of members of full class template specialization (full specialization is not a template - no "template <>" prefix)

void AClass<int>::MemberFunction()
{
    PRINTLN("full class template specialization (for int) member function");
}

float AClass<int>::StaticDataMember = 0.2f;

double AClass<int>::Inner::MemberFunction(int i)
{
    PRINTLN("full class template specialization (for int) nested class member function");
}

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

#include "specialization.hpp"

int main(int argc, char **argv)
{
    // ....

    AClass<int> ai;
    ai.MemberFunction();             // prints "full class template specialization (for int) member function"

    PRINTLN(ai.StaticDataMember);    // prints 0.2

    AClass<int>::Inner aii;
    aii.MemberFunction(10);          // prints "full class template specialization (for int) nested class member function"

    // ....
}

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:

// specialization.cpp

// definitions for member specialization of class template
// one enclosing class template, one template <> prefix

template <>
float AClass<float>::MemberFunction(const float &f)
{
    PRINTLN("class template member function specialization (for floats)");
}

template <>
float AClass<float>::StaticDataMember = 1.2f;

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:

// specialization.hpp

template <>
float AClass<float>::MemberFunction(const float &f);

template <>
float AClass<float>::StaticDataMember;

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:

// specialization.hpp

// definitions of nested member classes specializations can be placed inside the header file

template <>
struct AClass<float>::Inside
{
    void Function(int);
};

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):

// specialization.cpp

void AClass<float>::Inside::Function(int i)
{
    PRINTLN("class template nested class specialization (for floats) member function");
}

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

#include "specialization.hpp"

int main(int argc, char **argv)
{
    // ....

    AClass<float> af;
    af.MemberFunction(5.4f);        // prints "class template member function specialization (for floats)"

    PRINTLN(af.StaticDataMember);   // prints 1.2

    AClass<float>::Inside afi;
    afi.Function(1);                // prints "class template nested class specialization (for floats) member function"

    // ....
}

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

// specialization.hpp

template <>
void AClass<short>::Inside::Function();

// specialization.cpp

template <>
void AClass<short>::Inside::Function()
{
    PRINTLN("class template nested class member function specialization (for shorts)");
}

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

#include "specialization.hpp"

int main(int argc, char **argv)
{
    // ....

    AClass<short>::Inside asi;
    asi.Function();               // prints "class template nested class member function specialization (for shorts)"

    return 0;
}

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:

// specialization2.hpp 

/**** primary class template ****/

template <typename T>
class AClass
{
public:
    template <typename U>                            // member function template
    U MemberFunctionTemplate(T const &);
    template <typename S>                            // static member variable template
    static S StaticMemberVariableTemplate;
    template <typename V>                            // nested class template
    class Inner;
};

// class template member template definitions

template <typename T>
template <typename U>
U AClass<T>::MemberFunctionTemplate(const T &t)
{
    PRINTLN("class template member function template");
}

template <typename T>
template <typename S>
S AClass<T>::StaticMemberVariableTemplate = S();

template <typename T>
template <typename V>
class AClass<T>::Inner
{
public:
    void Function(); 
};

template <typename T> 
template <typename V>
void AClass<T>::Inner<V>::Function()
{
    PRINTLN("class template nested class template member function");
}

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

// specialization2.hpp

/**** class template full member template specializations */

// specialize member function template specifying enclosing class template parameters

template <>
template <typename U>
U AClass<float>::MemberFunctionTemplate(const float &f)
{
    PRINTLN("class template member function template specialization for class template parameter (float)");
}

// specialize static member variable template for enclosing class template parameters

template <>
template <typename S>
S AClass<float>::StaticMemberVariableTemplate{};

// specialize nested class template for enclosing class template parameters

template <>
template <typename V>
struct AClass<float>::Inner
{
    int AnotherFunction(double);
};

template <>
template <typename V>
int AClass<float>::Inner<V>::AnotherFunction(double d)
{
    PRINTLN("class template nested class template specialization for class template parameter (float) member function");
};

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):

// specialization2.hpp

// specialize member function template for both class template parameter and member template parameter (non defining declaration)

template <>
template <>
int AClass<float>::MemberFunctionTemplate(const float &f);

// specialize static member variable template for both class template parameter and member template parameter (non defining declaration)

template <>
template <>
int AClass<float>::StaticMemberVariableTemplate<int>;

// specialize nested class template for both outer class template parameter and nested class template parameter

template <>
template <>
struct AClass<float>::Inner<int>
{
    void YetAnotherFunction();
};

// specialization.cpp

#include "specialization.hpp"

// specialize for both class template parameter and member template parameter

template <>
template <>
int AClass<float>::MemberFunctionTemplate(const float &f)
{
    PRINTLN("class template member function template specialization for class template parameter (float) and member function template parameter (int)");
}

template <>
template <>
int AClass<float>::StaticMemberVariableTemplate<int> = 100;

// nested class template specialization is just a full class template specialization - no template <> needed

void AClass<float>::Inner<int>::YetAnotherFunction()
{
    PRINTLN("nested class template member function template specialization for class template parameter (float) and nested class template parameter (int)");
}

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

#include "specialization2.hpp"

int main(int argc, char *argv[])
{
    AClass<int> ai;
    ai.MemberFunctionTemplate<double>(10);          // prints "class template member function template"
    
    PRINTLN(ai.StaticMemberVariableTemplate<int>);  // prints 0

    AClass<int>::Inner<double> aii;
    aii.Function();                                 // prints "class template nested class template member function"

    AClass<float> af;
    af.MemberFunctionTemplate<double>(0.2f);        // prints "class template member function specialization for class template parameter (double)"
    af.MemberFunctionTemplate<int>(1.2);            // prints "class template member function specialization for class template parameter (float) and member function template parameter (int)"

    PRINTLN(af.StaticMemberVariableTemplate<double>);  // prints 0
    PRINTLN(af.StaticMemberVariableTemplate<int>);     // prints 100

    return 0;
}

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:

// specialization2.hpp

template <>
template <>
void AClass<short>::Inner<double>::Function();

// specialization2.cpp

#include "specialization2.hpp"

template <>
template <>
void AClass<short>::Inner<double>::Function()
{
    PRINTLN("class template nested class member function specialization for short and double");
}

// main.cpp

#include "specialization2.hpp"


#include "specialization.hpp"

int main(int argc, char *argv[])
{
    // ....

    AClass<short>::Inner<double> asd;
    asd.Function();            // prints "class template nested class member function specialization for short and double"
}

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *