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;
^