Templates with Special and Standard Behaviors

18 Nov 2018

Download inheritance.cpp Here

Download oneSpecial.cpp Here

Download aliasingExample.cpp Here

Download templateTemplate.cpp Here

Download noCompile.cpp Here

Download templateTemplateNoCompile.cpp Here

In this post we’ll look at several ways to use template specialization (including partial specialization) to give templates different behaviors depending on their inputs. I was inspired to look consider this while working on the penguinV; in that project there are classes whose interface are the same but they have different behavior for whether they deal with memory on the hosting computer memory or on a GPU. There are a small number of functions that deal with memory directly and many more functions that need deal with memory indirectly by making calls to the former functions. Code repetition is reduced by using dynamic resolution (i.e. at run time) of the functions that deal with memory directly.

This post is the result of my wondering if there is a way to make this resolution statically, i.e. at compile time. It turns out there are at least several ways to use C++ template metaprogramming to handle this problem in such a manner. I will review the ones I consider elegant enough.

Instead of looking at the issue of memory, we will look at creating a class template Foo<typename dataType, Qualifier qualifier> that stores a variable of type dataType. The template parameter qualifier will be of enumerated type Qualifier and can be either standard or special; furthermore, we will qualifier default to standard. The qualifier will determine the behavior of the member function Foo<dataType, qualifier>::bar().

Finally there will be one implementation of Foo<dataType, qualifier>::bar(int n) that calls Foo<dataType, qualifier>::bar() n times.

The catch to keep in mind is that C++ does not allow you to create a partial template specialization of a member function (such as Foo<dataType, qualifier>::bar()) without creating a separate definition for the partially specialized class. We intend for every value of qualifier to give a Foo with the same member functions, but their behavior should be different. Keeping multiple copies of effectively the same class declaration is certainly undesirable.

In my opinion, the best way to handle the “multiple copies” issue is to use inheritance to reduce the size of each copy. The copies are still there, but we can keep their size small.

As an example of how this looks for a user of your classes, consider the main execution code from inheritance.cpp:

// From inheritance.cpp 


int main() {

    Foo<int> standardX(1);
    Foo<int, special> specialX(2);

    // Test out Foo::bar().

    std::cout << "standardX.bar()" << std::endl;
    standardX.bar();
    std::cout << "\nspecialX.bar()" << std::endl;
    specialX.bar();

    // Test out Foo::bar(int).

    std::cout << "\nstandardX.bar(2)" << std::endl;
    standardX.bar(2);
    std::cout << "\nspecialX.bar(3)" << std::endl;
    specialX.bar(3);

    return 0;

This gives the output

standardX.bar()
Standard _myVar = 1

specialX.bar()
Special _myVar = 2

standardX.bar(2)
i = 0	Standard _myVar = 1
i = 1	Standard _myVar = 1

specialX.bar(3)
i = 0	Special _myVar = 2
i = 1	Special _myVar = 2
i = 2	Special _myVar = 2

So we make it easy for users of our classes to decide at compile time whether to use the standard or special behavior.

First, we’ll look at why a naive simple solution may not work (won’t even compile). Then we’ll look at the inheritance solution, followed by some other solutions that in my opinion aren’t as elegant.

Different Typedefs Do NOT Work

My original inspiration from the penguinV project is for the case of two special behaviors: work with memory on the hosting computer or work with memory on a GPU. For such a case, we really only want two different behaviors, and you may be tempted to just define different behaviors for different uses of typedef (so for this section we consider not using a type parameter typename dataType at all).

However, remember that typedef really just creates type synonyms; so it may not be possible to define different behavior for different instances of typedef. For example, consider the following code from noCompile.cpp; the compiler will have an error, because the the definitions of Foo<standardInt> and Foo<specialInt> are really two different definitions of Foo<int>.

/**
    noCompile.cpp

    Example that shows how just using typedef's for different behavior of using
    int's will NOT compile. Typedef's just provide type synonyms; so by defining
    Foo<standardInt> and Foo<specialInt>, our example is trying to give two different
    definitions for Foo<int>::bar().

*/

#include <iostream>

typedef int standardInt;
typedef int specialInt;

/**
    Class template for different behavior for dealing with integers. 
    @param dataType the dataType the class will be dealing with.
*/
template <typename dataType>
class Foo {

    public:
    Foo(dataType myVar_) : _myVar(myVar_) {}
  
    /**
        Behavior of bar() should depend on dataType.
    */ 
    void bar();
    
    private:
    dataType _myVar; 
}; 

/**
    For type standardInt, bar() should just print adding 1 to _myVar.   
*/
template <>
void Foo<standardInt>::bar() {
    std::cout << "standardInt _myVar = " << _myVar << std::endl;
}

/**
    For type specialInt, bar() should just print multiplying _myVar by 3. 

    BUT THERE IS A PROBLEM! The names specialInt and standardInt are just other names for
    the type int. So we are trying to the the template two different definitions for 
    template parameter int, i.e. two different defintions of Foo<int>::bar().

    So this will NOT compile!
*/
template <>
void Foo<specialInt>::bar() {
    std::cout << "specialInt _myVar = " _myVar << std::endl; 
}

typedef Foo<standardInt> FooInt; /**< A special typedef to hide the template usage. */

int main() {

    Foo<standardInt> x(1);
    FooInt x2(1);
    Foo<specialInt> y(1);

    std::cout << "x.bar()" << std::endl;
    x.bar();

    std::cout << "\nx2.bar()" << std::endl;
    x2.bar();

    std::cout << "\nx3.bar()" << std::endl;
    y.bar();

    return 0;
}

Using Inheritance

Now we look at using partial template specialization in combination with inheritance to minimize the amount of code we need to copy. We put the separate implementations of bar() inside a base class template _Bar<typename dataType, Qualifier qualifier>. Here we are using the convention that the underscore in _Bar should tell the user that this class should be considered “private” to inheritance.cpp.

Take note of the fact that we need to make separate definitions for the base class template _Bar; that is we need to specify more than just the partial specializations of the member function _Bar<dataType, qualifier>::bar(). However, the code copying is minimal compared to copying all of Foo.

/**
    inheritance.cpp

    Use inheritance and template specialization to implement a class template that takes a datatype and a qualifier; 
    the qualifier comes in two states: standard or special. The class template should have the same interface 
    but two different behaviors for a member function bar() based on the qualifier. 

    The catch is that member function template partial specialization requires the class template partial
    specialization to also be defined. The use of inheritance allows us to minimize the need to rewrite code
    for the class template partial specialization. 
*/

#include <iostream>

enum Qualifier {standard, special};

/** 
    Class template _Bar holds the implementations of bar(). The general case
    is meant to be the standard case of the template.

    We put _myVar in _Bar since it is needed for function bar().

    @tparam dataType The type of _myVar, the variable held by _Bar.
    @tparam qualifier The qualifier as to whether to use the standard behavior or the
                 special behavior.
*/

template <typename dataType, Qualifier qualifier> 
class _Bar {

    public:

    /**
        The standard version of bar(). 
    */
    void bar() {

        std::cout << "Standard _myVar = " << _myVar << std::endl;
    }

    protected:

    dataType _myVar;

};

/**
    The partial specialization of class template _Bar that gives the
    special behavior of bar().

    @tparam dataType The datatype of _Bar::_myVar.
*/

template <typename dataType>
class _Bar<dataType, special> {

    public:
    
    void bar() {

        std::cout << "Special _myVar = " << _myVar << std::endl;

    }

    protected:

    dataType _myVar;
};

/**
    The class template Foo defaults to have the standard qualifier. The
    member function bar() will make calls to the particular version of
    bar() created by the super-class template _Bar.

    @tparam dataType The datatype of _Bar::_myVar.
    @tparam qualifier The qualifier for whether to use standard behavior or
                 to use special behavior. Default: standard.
*/

template <typename dataType, Qualifier qualifier = standard>
class Foo : public _Bar<dataType, qualifier> {

    public:
    Foo(dataType myVar_) {
        // Note the need for _Bar<dataType, qualifier>:: to find the variable.
        _Bar<dataType, qualifier>::_myVar = myVar_;
    } 

    // Need to tell compiler to look for the other overloaded version of bar(). 
    using _Bar<dataType, qualifier>::bar; 

    /**
        Calls bar n times, each time printing out the count.

        @param n The number of times to call Foo::bar().
    */ 
    void bar(int n) {
        for(int i = 0; i < n; i++) {
            std::cout << "i = " << i << "\t";
            bar();
        }
    }
    
}; 

int main() {

    Foo<int> standardX(1);
    Foo<int, special> specialX(2);

    // Test out Foo::bar().

    std::cout << "standardX.bar()" << std::endl;
    standardX.bar();
    std::cout << "\nspecialX.bar()" << std::endl;
    specialX.bar();

    // Test out Foo::bar(int).

    std::cout << "\nstandardX.bar(2)" << std::endl;
    standardX.bar(2);
    std::cout << "\nspecialX.bar(3)" << std::endl;
    specialX.bar(3);

    return 0;
}

Using Friend Classes

Another option is to use a friend class to implement the functions with different behavior. Now we use a class template _Bar to hold the different versions of bar(). The catch is that we need to pass an explicit pointer or reference to the class calling _Bar::bar(); so really, the parameter format is Bar<dataType, qualifier>::bar(Foo<dataType, qualifier>*).

Then, inside the class template Foo we can make a general member function Foo::bar() that will call the appropriate _Bar<dataType, qualifier>::bar(Foo<dataType, qualifier> *). Of course, we will need to make _Bar a friend of Foo. If we wish to restrict any other classes from calling _Bar then we can make everything inside _Bar private and also make Foo a friend of _Bar.

/**
    oneSpecial.cpp

    Use a friend class template and template specialization to implement a class template that takes 
    a datatype and a qualifier; the qualifier comes in two states: standard or special. The class 
    template should have the same interface but two different behaviors for a member function 
    bar() based on the qualifier. 

    To minimize rewriting code, the behavior of bar() is implemented in a separate class template
    _Bar which is made a friend of Foo. Then _Bar::bar() is called from inside
    Foo::bar(). 

*/

#include <iostream>

enum Qualifier {standard, special};

// Need to declare the class template to use with its friend.
template <typename dataType, Qualifier qualifier> class _Bar;

/**
    The class template Foo has friend class template _Bar.
    The partial specializations are defined in _Bar. Also, the _Bar template is a friend of Foo.

    @tparam dataType The type of _myVar that the class holds. This will be printed
                     out using bar().
    @tparam qualifier The qualifier to specify whether to use standard behavior or special
                 behavior for Foo::Bar.
*/
template <typename dataType, Qualifier qualifier = standard>
class Foo {

    friend class _Bar<dataType, qualifier>;

    public:
    Foo(dataType myVar_) : _myVar(myVar_) {}

    /** 
        The behavior of bar() is defined by the version of bar() given inside the _Bar template.
    */   
    void bar() { _Bar<dataType, qualifier>::bar(this); }

    /**
        Calls bar() n times, outputting the count each time.
    */
    void bar(int n) {
        for(int i = 0; i < n; i++) {
            std::cout << "i = " << i << "\t"; 
            bar();
        }
    }
    
    private:
    dataType _myVar;
}; 

/**
    Structure template that creates the function bar(). It is a friend of Foo so that
    it can access the members of Foo.

    The general template is for the standard behavior given by passing qualifier = standard.
    @tparam dataType The datatype of Foo::_myVar that will be printed.
    @tparam qualifier Whether to use the standard behavior or the special behavior.
*/
template <typename dataType, Qualifier qualifier>
class _Bar{

    friend class Foo<dataType, qualifier>;

    private:
    /**
        Standard behavior for bar().        
        @param my Pointer to the instance of Foo<dataType, qualifier> that called bar(), e.g. the 
                  Foo object's this pointer.
    */
    static void bar(Foo<dataType, qualifier>* my) {
        std::cout << "Standard x = " << my->_myVar <<std::endl;
    }
};

/**
    This specialization for qualifier = special gives the special behavior of bar().

    @tparam dataType The type of Foo::_myVar that will be printed out.
*/
template <typename dataType>
class _Bar<dataType, special> {
    
    friend class Foo<dataType, special>;

    private:

    /**
        Special behavior for bar().
        @param my Pointer to the instance of Foo<dataType, qualifier> that called bar(), e.g. the
                  Foo object's this pointer.
    */
    static void bar(Foo<dataType, special>* my) {
        std::cout << "Special x = " << my->_myVar << std::endl;
    }
};

int main() {

    Foo<int> standardX(1);
    Foo<int, special> specialX(2);

    // Test out Foo::bar() for each object.

    std::cout << "standardX.bar()" << std::endl;
    standardX.bar();
    std::cout << "\nspecialX.bar()" << std::endl;
    specialX.bar();

    // Test out Foo::bar(int) for each object.

    std::cout << "\nstandardX.bar(2)" << std::endl;
    standardX.bar(2);
    std::cout << "\nspecialX.bar(3)" << std::endl;
    specialX.bar(3);

    return 0;
}

Using Aliasing For a Finite Number of Possibilities

What if we aren’t interested in creating behavior for all possible outputs? For example how can achieve standard behavior for int and double; special behavior for int; and no other behavior? A quick trick is to use an enumerative type to determine the behavior; this allows us to get two different behaviors for int. However, then we are left with the problem of generating the type of _myVar using a non-type 4 template parameter. This can be accomplished using aliasing.

/**
   aliasingExample.cpp

   Restrict possible outputs of Foo class tempalte to a finite number by using a non-type parameter, in particular 
   use values of a special enumerative type. 
*/

#include <iostream>

enum DataTypeId {standardInt, specialInt, standardDouble};

/** A structure template to hold the type of data to hold inside the template class Foo.
    Will be used with aliasing.
*/
template <DataTypeId Id> struct DataTypeHolder;

template <>
struct DataTypeHolder<standardInt> {
    typedef int type; 
};

template <>
struct DataTypeHolder<specialInt> {
    typedef int type;
};

template <>
struct DataTypeHolder<standardDouble> {
    typedef double type;   
};

/**
    Alisaing tempalte that gives the data type for Foo::_myVar.
*/
template <DataTypeId Id>
using _myVarType = typename DataTypeHolder<Id>::type;

template <DataTypeId Id>
class Foo {

    // For shorthand, can use typedef to make shortcut for _myVarType<Id>.
    typedef _myVarType<Id> datatype;

    public:
    Foo(datatype myVar_) : _myVar(myVar_) {}

    /**
        The behavior of bar() will depend on the template parameter Id.
        No matter what Id is, bar() will print out the value of _myVar, but
        it will also print out a message depending on the value of Id.
    */   
    void bar();

    /**
        Calls bar() n times, each time printing out the count.

        @param n The number of times to call bar().
    */
    void bar(int n);
    
    private:
    datatype _myVar;
}; 

template <DataTypeId Id>
void Foo<Id>::bar(int n) {

    for (int i = 0; i < n; i++) {
        std::cout << "i = " << i << "\t";    
        bar();
    }
}


template <>
void Foo<standardInt>::bar() {
 std::cout << "standardInt " << _myVar << std::endl;
}

template <>
void Foo<specialInt>::bar() {
    std::cout << "specialInt " << _myVar << std::endl;
}

template <>
void Foo<standardDouble>::bar() {
    std::cout << "standardDouble " << _myVar << std::endl;
}

// One can use typedef to make shorthand versions of each type if needed/desired.
typedef Foo<standardInt> FooInt;

int main() {

    Foo<standardInt> x(1);
    FooInt x2(2);
    Foo<specialInt> y(3);
    Foo<standardDouble> z(4.5);

    std::cout << "x.bar()" <<std::endl;
    x.bar();
    std::cout << "\nx2.bar()" << std::endl;
    x2.bar();
    std::cout << "\ny.bar()" << std::endl;
    y.bar();
    std::cout << "\nz.bar()" << std::endl;
    z.bar();

    std::cout << "\nx.bar(5)" << std::endl;
    x.bar(5);

    return 0;
}

Using Template Template Parameters

The final possibility we list is using a template parameter that is itself a template (a so called template template parameter). This example uses inheritance, but now we pass a class template to generate the super class. However, the super class must implement the member function bar() and hold a variable _myVar. If we pass a class template that fails to do so, then we will generate compiler errors. So in a sense this method is less safe than the methods shown above since it is possible for the user to generate code that won’t compile. However, this can be avoided by proper documentation.

/**
    templateTemplate.cpp

    Use inheritance and a template template parameter to specify the behavior of Foo::bar().
 
    Now we pass in a class template that generates the behavior of bar(). The catch is that if the
    user passes in a class template that doesn't generate the member function bar() or hold 
    the member variable _myVar, then there will be compiler errors.
*/

#include <iostream>

/**
    Class template that implements the standard behavior of bar().

    @tparam dataType The type of _myVar.
*/

template <typename dataType>
class StandardBar {

    public:

    /**
        The standard version of bar(). 
    */
    void bar() {

        std::cout << "Standard _myVar = " << _myVar << std::endl;
    }

    protected:

    dataType _myVar;

};

/**
    The class template giving the special version of bar().

    @tparam dataType The type of _myVar.
*/

template <typename dataType>
class SpecialBar {

    public:
    
    void bar() {

        std::cout << "Special _myVar = " << _myVar << std::endl;

    }

    protected:

    dataType _myVar;

};

/**
    The class template Foo defaults to have the standard version of bar(). The
    member function bar(int n) will make calls to the particular version of
    bar() created by the super-class template Bar.

    @tparam dataType The datatype of _Bar::_myVar.
    @tparam Bar The class template that gives the version of bar() and holds _myVar. Default
                is StandardBar. 
*/

template <typename dataType, template<typename> class Bar = StandardBar> 
class Foo : public Bar<dataType> {

    public:
    Foo(dataType myVar_) {

        // Note the need for Bar<dataType>:: to find the variable.
        Bar<dataType>::_myVar = myVar_;
    } 

    // Need to tell compiler to look for the other overloaded version of bar(). 
    using Bar<dataType>::bar; 

    /**
        Calls bar n times, each time printing out the count.

        @param n The number of times to call Foo::bar().
    */ 
    void bar(int n) {
        for(int i = 0; i < n; i++) {
            std::cout << "i = " << i << "\t";
            bar();
        }
    }
    
}; 

/**
    Create an alias to make using Foo<dataType, SpecialBar> easier on the user.
*/

template <typename dataType>
using FooSpecial = Foo<dataType, SpecialBar>;

int main() {

    Foo<int> standardX(1);
    FooSpecial<int> specialX(2);

    // Test out Foo::bar().

    std::cout << "standardX.bar()" << std::endl;
    standardX.bar();
    std::cout << "\nspecialX.bar()" << std::endl;
    specialX.bar();

    // Test out Foo::bar(int).

    std::cout << "\nstandardX.bar(2)" << std::endl;
    standardX.bar(2);
    std::cout << "\nspecialX.bar(3)" << std::endl;
    specialX.bar(3);

    return 0;
}

Let’s take a look at an example where compilation will fail if we don’t implement the super class template correctly. The problem with the following code is that the class template NoBar doesn’t implement the member function bar().

/**
    templateTemplateNoCompile.cpp

    This is an example of the classes in templateTemplate.cpp not compiling if the super-class doesn't
    implement bar().
*/

#include <iostream>

/**
    Class template for standard bar().

    @tparam dataType The type of _myVar.
*/
template <typename dataType>
class StandardBar {

    public:

    /**
        The standard version of bar(). 
    */
    void bar() {

        std::cout << "Standard _myVar = " << _myVar << std::endl;
    }

    protected:

    dataType _myVar;

};

/**
    This is missing the member function bar().

    @tparam dataType The type of _myVar.
*/
template <typename dataType>
class NoBar {

    protected:

    dataType _myVar;

};

/**
    The class template Foo defaults to have the standard qualifier. The
    member function bar(int n) will make calls to the particular version of
    bar() created by the super-class template _Bar.

    @tparam dataType The datatype of Bar::_myVar.
    @tparam Bar The super class template implementing the member function bar() and
                holds the member variable _myVar.
*/

template <typename dataType, template<typename> class Bar = StandardBar> 
class Foo : public Bar<dataType> {

    public:
    Foo(dataType myVar_) {
        // Note the need for Bar<dataType>:: to find the variable.
        Bar<dataType>::_myVar = myVar_;
    } 

    // Need to tell compiler to look for the other overloaded version of bar(). 
    using Bar<dataType>::bar; 

    /**
        Calls bar n times, each time printing out the count.

        @param n The number of times to call Foo::bar().
    */ 
    void bar(int n) {
        for(int i = 0; i < n; i++) {
            std::cout << "i = " << i << "\t";
            bar();
        }
    }
    
}; 

int main() {

    Foo<int> standardX(1);
    Foo<int, NoBar> noCompileX(2);

}

When we try to compile this with g++ templateTemplateNoCompile.cpp -std=c++11, we get the following error messages:

templateTemplateNoCompile.cpp: In instantiation of 'class Foo<int, NoBar>':
templateTemplateNoCompile.cpp:87:31:   required from here
templateTemplateNoCompile.cpp:68:26: error: no members matching 'NoBar<int>::bar' in 'class NoBar<int>'
     using Bar<dataType>::bar;
                          ^

Download inheritance.cpp Here

Download oneSpecial.cpp Here

Download aliasingExample.cpp Here

Download templateTemplate.cpp Here

Download noCompile.cpp Here

Download templateTemplateNoCompile.cpp Here