Chapter 20

Advanced Templates

WHAT’S IN THIS CHAPTER?

  • What the details are of the different kinds of template parameters
  • How to use partial specialization
  • How to write recursive templates
  • How to write type-safe variable arguments functions using variadic templates
  • What metaprogramming is and how to use it

The previous chapter covered the most widely used features of class and function templates. If you are interested in only a basic knowledge of templates so that you can better understand how the STL works, or perhaps write your own simple classes, you can skip this chapter on advanced templates. However, if templates interest you and you want to uncover their full power, continue reading this chapter to learn about some of the more obscure, but fascinating, details.

MORE ABOUT TEMPLATE PARAMETERS

There are actually three kinds of template parameters: type, non-type, and template template (no, you’re not seeing double: that really is the name). You’ve seen examples of type and non-type parameters in the previous chapter, but not template template parameters yet. There are also some tricky aspects to both type and non-type parameters that are not covered in the previous chapter.

More about Template Type Parameters

Type parameters to templates are the main purpose of templates. You can declare as many type parameters as you want. For example, you could add to the grid template a second type parameter specifying another templatized class container on which to build the grid. The standard template library defines several templatized container classes, including vector and deque, which are introduced in Chapter 12. In your original grid class you might want to have an array of vectors or an array of deques instead of just an array of arrays. With another template type parameter, you can allow the user to specify whether she wants the underlying container to be a vector or a deque. Here is the class definition with the additional template parameter:

image
template <typename T, typename Container>
class Grid
{
    public:
        Grid(size_t inWidth = kDefaultWidth,
            size_t inHeight = kDefaultHeight);
       Grid(const Grid<T, Container>& src);
        virtual ~Grid();
       Grid<T, Container>& operator=(const Grid<T, Container>& rhs);
        void setElementAt(size_t x, size_t y, const T& inElem);
        T& getElementAt(size_t x, size_t y);
        const T& getElementAt(size_t x, size_t y) const;
        size_t getHeight() const { return mHeight; }
        size_t getWidth() const { return mWidth; }
        static const size_t kDefaultWidth = 10;
        static const size_t kDefaultHeight = 10;
    protected:
       void copyFrom(const Grid<T, Container>& src);
       Container* mCells;
        size_t mWidth, mHeight;
};

Code snippet from GridTemplateContainerGrid.h

This template now has two parameters: T and Container. Thus, wherever you previously referred to Grid<T> you must now refer to Grid<T, Container> to specify both template parameters. The only other change is that mCells is now a pointer to a dynamically allocated array of Containers instead of a pointer to a dynamically allocated two-dimensional array of T elements.

Here is the constructor definition. It assumes that the Container type has a resize() method. If you try to instantiate this template by specifying a type that has no resize() method, the compiler will generate an error:

image
template <typename T, typename Container>
Grid<T, Container>::Grid(size_t inWidth, size_t inHeight) :
    mWidth(inWidth), mHeight(inHeight)
{
    // Dynamically allocate the array of mWidth containers
    mCells = new Container[mWidth];
    for (size_t i = 0; i < mWidth; i++) {
        // Resize each container so that it can hold mHeight elements.
        mCells[i].resize(mHeight);
    }
}

Code snippet from GridTemplateContainerGrid.h

Here is the destructor definition. There’s only one call to new in the constructor, so only one call to delete in the destructor.

image
template <typename T, typename Container>
Grid<T, Container>::~Grid()
{
    delete [] mCells;
    mCells = nullptr;
}

Code snippet from GridTemplateContainerGrid.h

The code in copyFrom() assumes that you can access elements in the container by using array [] notation. Chapter 18 explains how to overload the [] operator to implement this feature in your own container classes. Both vector and deque from the STL support this syntax.

image
template <typename T, typename Container>
void Grid<T, Container>::copyFrom(const Grid<T, Container>& src)
{
    mWidth = src.mWidth;
    mHeight = src.mHeight;
    mCells = new Container[mWidth];
    for (size_t i = 0; i < mWidth; i++) {
       // Resize each element, as in the constructor.
       mCells[i].resize(mHeight);
    }
    for (size_t i = 0; i < mWidth; i++) {
        for (size_t j = 0; j < mHeight; j++) {
            mCells[i][j] = src.mCells[i][j];
        }
    }
}

Code snippet from GridTemplateContainerGrid.h

Here are the implementations of the remaining methods:

image
template <typename T, typename Container>
Grid<T, Container>::Grid(const Grid<T, Container>& src)
{
    copyFrom(src);
}
template <typename T, typename Container>
Grid<T, Container>& Grid<T, Container>::operator=(     const Grid<T, Container>& rhs)
{
    // Check for self-assignment.
    if (this == &rhs) {
        return *this;
    }
    // Free the old memory.
    delete [] mCells;
    mCells = nullptr;
    // Copy the new memory.
    copyFrom(rhs);
    return *this;
}
template <typename T, typename Container>
void Grid<T, Container>::setElementAt(size_t x, size_t y, const T& inElem)
{
    mCells[x][y] = inElem;
}
template <typename T, typename Container>
T& Grid<T, Container>::getElementAt(size_t x, size_t y)
{
    return mCells[x][y];
}
template <typename T, typename Container>
const T& Grid<T, Container>::getElementAt(size_t x, size_t y) const
{
    return mCells[x][y];
}

Code snippet from GridTemplateContainerGrid.h

Now you can instantiate and use Grid objects like this:

image
Grid<int, vector<int>> myIntGrid;
Grid<int, deque<int>> myIntGrid2;
myIntGrid.setElementAt(3, 4, 5);
cout << myIntGrid.getElementAt(3, 4); 
Grid<int, vector<int>> grid2(myIntGrid);
grid2 = myIntGrid;

Code snippet from GridTemplateContainerGridTest.cpp

The use of the word Container for the parameter name doesn’t mean that the type really must be a container. You could try to instantiate the Grid class with an int instead:

Grid<int, int> test; // WILL NOT COMPILE

This line will not compile, but it might not give you the error you expect. It won’t complain that the second type argument is an int instead of a container. Instead it will tell you that left of '.resize' must have class/struct/union type. That’s because the compiler attempts to generate a Grid class with int as the Container. Everything works fine until it tries to compile this line:

mCells[i].resize(mHeight);

At that point, the compiler realizes that mCells[i] is an int, so you can’t call the resize() method on it.

This approach is used in the STL. The stack, queue, and priority_queue class templates all take a template type parameter specifying the underlying container, which can be a vector, deque, or list.

Default Values for Template Type Parameters

You can give template parameters default values. For example, you might want to say that the default container for your Grid is a vector. The template class definition would look like this:

image
template <typename T, typename Container = vector<T>>
class Grid
{
    public:
        // Everything else is the same as before.
};

Code snippet from GridTemplateContainerGridDefault.h

You can use the type T from the first template parameter as the argument to the vector template in the default value for the second template parameter. In versions of C++ earlier than C++11, there must be a space between the two closing angle brackets. C++11 does not require this space anymore.

The C++ syntax requires that you do not repeat the default value in the template header line for method definitions.

With this default parameter, clients can now instantiate a grid with or without specifying an underlying container:

image
Grid<int, vector<int>> myIntGrid;
Grid<int> myIntGrid2;

Code snippet from GridTemplateContainerGridDefaultTest.cpp

Introducing Template Template Parameters

There is one problem with the Container parameter in the previous section. When you instantiate the class template, you write something like this:

Grid<int, vector<int>> myIntGrid;

Note the repetition of the int type. You must specify that it’s the element type both of the Grid and of the vector. What if you wrote this instead?

Grid<int, vector<SpreadsheetCell>> myIntGrid;

That wouldn’t work very well. It would be nice to be able to write the following, so that you couldn’t make that mistake:

Grid<int, vector> myIntGrid;

The Grid class should be able to figure out that it wants a vector of ints. The compiler won’t allow you to pass that argument to a normal type parameter though, because vector by itself is not a type, but a template.

If you want to take a template as a template parameter, you must use a special kind of parameter called a template template parameter. The syntax is crazy, but, if you’re still interested, read on.

Specifying a template template parameter is sort of like specifying a function pointer parameter in a normal function. Function pointer types include the return type and parameter types of a function. Similarly, when you specify a template template parameter, the full specification of the template template parameter includes the parameters to that template.

Containers in the STL have a template parameter list that looks something like this:

template <typename E, typename Allocator = allocator<E> >
class vector
{
    // Vector definition
};

The E parameter is the element type. The Allocator parameter is covered in Chapter 12.

Given the preceding template specification, here is the template class definition for the Grid class that takes a container template as its second template parameter:

image
template <typename T,
  template <typename E, typename Allocator = allocator<E>> class Container
    = vector>
class Grid
{
    public:
        // Omitted code that is the same as before
    protected:
        Container<T>* mCells;
        // Omitted code that is the same as before
};

Code snippet from GridTemplateContainerGridTemplateTemplate.h

What is going on here? The first template parameter is the same as before: the element type T. The second template parameter is now a template itself for a container such as vector or deque. As you saw earlier, this “template type” must take two parameters: an element type E and an allocator Allocator. Note the repetition of the word class after the nested template parameter list. The name of this parameter in the Grid template is Container (as before). The default value is now vector, instead of vector<T>, because the Container is a template instead of an actual type.

The syntax rule for a template template parameter more generically is this:

template <..., template <TemplateTypeParams> class ParameterName, ...>

Now that you’ve suffered through the syntax to declare the template, the rest is easy. Instead of using Container by itself in the code, you must specify Container<T> as the container type you use. For example, the constructor now looks like this (you don’t repeat the default template template parameter argument in the template specification for the method definition):

image
template <typename T,
  template <typename E, typename Allocator = allocator<E>> class Container>
  Grid<T, Container>::Grid(size_t inWidth, size_t inHeight) :
    mWidth(inWidth), mHeight(inHeight)
{
    mCells = new Container<T>[mWidth];
    for (size_t i = 0; i < mWidth; i++) {
        mCells[i].resize(mHeight);
    }
}

Code snippet from GridTemplateContainerGridTemplateTemplate.h

After implementing all the methods, you can use the template as follows:

image
Grid<int, vector> myGrid;
myGrid.setElementAt(1, 2, 3);
myGrid.getElementAt(1, 2);
Grid<int, vector> myGrid2(myGrid);

Code snippet from GridTemplateContainerGridTemplateTemplateTest.cpp

This C++ syntax is a bit convoluted because it is trying to allow for maximum flexibility. Try not to bog down in the syntax here, and keep the main concept in mind: You can pass templates as parameters to other templates.

More about Non-Type Template Parameters

You might want to allow the user to specify an empty (not in the literal sense) element that is used to initialize each cell in the grid. Here is a perfectly reasonable approach to implement this goal:

image
template <typename T, const T EMPTY>
class Grid
{
    public:
        Grid(size_t inWidth = kDefaultWidth,
            size_t inHeight = kDefaultHeight);
       Grid(const Grid<T, EMPTY>& src);
        virtual ~Grid();
       Grid<T, EMPTY>& operator=(const Grid<T, EMPTY>& rhs);
        // Omitted for brevity
    protected:
        void copyFrom(const Grid<T, EMPTY>& src);
        T** mCells;
        size_t mWidth, mHeight;
};

Code snippet from GridEmptyGrid.h

This definition is legal. You can use the type T from the first parameter as the type for the second parameter, and non-type parameters can be const just like function parameters. You can use this initial value for T to initialize each cell in the grid:

image
template <typename T, const T EMPTY>
Grid<T, EMPTY>::Grid(size_t inWidth, size_t inHeight) :
    mWidth(inWidth), mHeight(inHeight)
{
    mCells = new T* [mWidth];
    for (size_t i = 0; i < mWidth; i++) {
        mCells[i] = new T[mHeight];
        for (size_t j = 0; j < mHeight; j++) {
            mCells[i][j] = EMPTY;
        }
    }
}

Code snippet from GridEmptyGrid.h

The other method definitions stay the same, except that you must add the second type parameter to the template lines, and all the instances of Grid<T> become Grid<T, EMPTY>. After making those changes, you can then instantiate an int Grid with an initial value for all the elements:

image
Grid<int, 0> myIntGrid;
Grid<int, 10> myIntGrid2;

Code snippet from GridEmptyGridTest.cpp

The initial value can be any integer you want. However, suppose that you try to create a SpreasheetCell Grid:

image
SpreadsheetCell emptyCell;
Grid<SpreadsheetCell, emptyCell> mySpreadsheet; // WILL NOT COMPILE

Code snippet from GridEmptyGridTest.cpp

That line leads to a compiler error because you cannot pass objects as arguments to non-type parameters.

cross.gif

Non-type parameters cannot be objects, or even doubles or floats. They are restricted to integral types, enums, pointers, and references.

This example illustrates one of the vagaries of template classes: They can work correctly on one type but fail to compile for another type.

Reference and Pointer Non-Type Template Parameters

A more comprehensive way of allowing the user to specify an initial empty element for the grid uses a reference to a T as the non-type template parameter. Here is the new class definition:

image
template <typename T, const T& EMPTY>
class Grid
{
    // Everything else is the same as the previous example, except the
    // template lines in the method definitions specify const T& EMPTY
    // instead of const T EMPTY.
};

Code snippet from GridEmptyGridRefNonType.h

Now you can instantiate this template class for any type. However, the reference you pass as the second template argument must refer to a global variable with external linkage. Chapter 9 discusses external linkage, which can be thought of as the opposite of static linkage, and just means that the variable is available in source files outside the one in which it is defined. You can declare that a variable has external linkage with the extern keyword:

extern const int x = 0;

Note that this line occurs outside of any function or method body. Here is a program that declares int and SpreadsheetCell grids with initialization parameters:

image
extern const int emptyInt = 0;
extern const SpreadsheetCell emptyCell(0);
int main()
{
    Grid<int, emptyInt> myIntGrid;
    Grid<SpreadsheetCell, emptyCell> mySpreadsheet;
    Grid<int, emptyInt> myIntGrid2(myIntGrid);
    return 0;
}

Code snippet from GridEmptyGridTestRefNonType.cpp

cross.gif

Reference and pointer template arguments must refer to global variables that are available from all translation units. The technical term for these types of variables is data with external linkage.

Using Zero-Initialization of Template Types

Neither of the options presented so far for providing an initial empty value for the cells is very attractive. Instead, you may simply want to initialize each cell to a reasonable default value that you choose (instead of allowing the user to specify). Of course, the immediate question is: What’s a reasonable value for any possible type? For objects, a reasonable value is an object created with the default constructor. In fact, that’s exactly what you’re already getting when you create an array of objects. However, for simple data types like integral types (int, short, ...), a reasonable initial value is 0; for floating point types, a reasonable initial value is 0.0; and for pointers, a reasonable initial value is nullptr. Therefore, what you really want to be able to do is assign this initial value to non-objects and use the default constructor on objects. You actually saw the syntax for this behavior in the section on “Method Templates with Non-Type Parameters” in the previous chapter. Here is the implementation of the Grid template constructor using the zero-initialization syntax:

image
template <typename T>
Grid<T>::Grid(size_t inWidth, size_t inHeight) :
    mWidth(inWidth), mHeight(inHeight)
{
    mCells = new T* [mWidth];
    for (size_t i = 0; i < mWidth; i++) {
        mCells[i] = new T[mHeight];
        for (size_t j = 0; j < mHeight; j++) {
            mCells[i][j] = T();
        }
    }
}

Code snippet from GridEmptyGridZeroInitialized.h

Given this ability, you can revert to the original Grid class (without an EMPTY non-type parameter) and just initialize each cell element to its zero-initialized “reasonable value.”

TEMPLATE CLASS PARTIAL SPECIALIZATION

The char* class specialization shown in the previous chapter is called full template class specialization because it specializes the Grid template for every template parameter. There are no template parameters left in the specialization. That’s not the only way you can specialize a class; you can also write a partial class specialization, in which you specialize some template parameters but not others. For example, recall the basic version of the Grid template with width and height non-type parameters:

image
template <typename T, size_t WIDTH, size_t HEIGHT>
class Grid
{
    public:
        void setElementAt(size_t x, size_t y, const T& inElem);
        T& getElementAt(size_t x, size_t y);
        const T& getElementAt(size_t x, size_t y) const;
        size_t getHeight() const { return HEIGHT; }
        size_t getWidth() const { return WIDTH; }
    protected:
        T mCells[WIDTH][HEIGHT];
};

Code snippet from GridPartialStringGrid.h

You could specialize this template class for char* C-style strings like this:

image
#include "Grid.h" // The file containing the Grid template definition
template <size_t WIDTH, size_t HEIGHT>
class Grid<char*, WIDTH, HEIGHT>
{
    public:
        Grid();
       Grid(const Grid<char*, WIDTH, HEIGHT>& src);
        virtual ~Grid();
       Grid<char*, WIDTH, HEIGHT>& operator=(
            const Grid<char*, WIDTH, HEIGHT>& rhs);
       void setElementAt(size_t x, size_t y, const char* inElem);
       char* getElementAt(size_t x, size_t y) const;
        size_t getHeight() const { return HEIGHT; }
        size_t getWidth() const { return WIDTH; }
    protected:
       void copyFrom(const Grid<char*, WIDTH, HEIGHT>& src);
       char* mCells[WIDTH][HEIGHT];
};

Code snippet from GridPartialStringGridString.h

In this case, you are not specializing all the template parameters. Therefore, your template line looks like this:

template <size_t WIDTH, size_t HEIGHT>
class Grid<char*, WIDTH, HEIGHT>

Note that the template has only two parameters: WIDTH and HEIGHT. However, you’re writing a Grid class for three arguments: T, WIDTH, and HEIGHT. Thus, your template parameter list contains two parameters, and the explicit Grid<char*, WIDTH, HEIGHT> contains three arguments. When you instantiate the template, you must still specify three parameters. You can’t instantiate the template with only height and width:

image
Grid<int, 2, 2> myIntGrid;      // Uses the original Grid
Grid<char*, 2, 2> myStringGrid; // Uses the partial specialization for char *s
Grid<2, 3> test;                // DOES NOT COMPILE! No type specified.

Code snippet from GridPartialStringGridTestString.cpp

Yes, the syntax is confusing. And it gets worse. In partial specializations, unlike in full specializations, you include the template line in front of every method definition:

image
template <size_t WIDTH, size_t HEIGHT>
Grid<char*, WIDTH, HEIGHT>::Grid()
{
    for (size_t i = 0; i < WIDTH; i++) {
        for (size_t j = 0; j < HEIGHT; j++) {
            mCells[i][j] = nullptr; // Initialize each element to nullptr.
        }
    }
}

Code snippet from GridPartialStringGridString.h

You need this template line with two parameters to show that this method is parameterized on those two parameters. Note that wherever you refer to the full class name, you must use Grid<char*, WIDTH, HEIGHT>.

You can find the rest of the method definitions in the downloadable source code archive for this book on www.wrox.com.

Another Form of Partial Specialization

The previous example does not show the true power of partial specialization. You can write specialized implementations for a subset of possible types without specializing individual types. For example, you can write a specialization of the Grid class for all pointer types. This specialization might perform deep copies of objects to which pointers point instead of storing shallow copies of the pointers in the grid.

Here is the class definition, assuming that you’re specializing the initial version of the Grid with only one parameter:

image
#include "Grid.h"
template <typename T>
class Grid<T*>
{
    public:
        Grid(size_t inWidth = kDefaultWidth,
            size_t inHeight = kDefaultHeight);
        Grid(const Grid<T*>& src);
        virtual ~Grid();
        Grid<T*>& operator=(const Grid<T*>& rhs);
        void setElementAt(size_t x, size_t y, T* inElem);
        T* getElementAt(size_t x, size_t y) const;
        size_t getHeight() const { return mHeight; }
        size_t getWidth() const { return mWidth; }
        static const size_t kDefaultWidth = 10;
        static const size_t kDefaultHeight = 10;
    protected:
        void copyFrom(const Grid<T*>& src);
        T*** mCells;
        size_t mWidth, mHeight;
}; 

Code snippet from GridPartialPtrGridPtr.h

As usual, these two lines are the crux of the matter:

template <typename T>
class Grid<T*>

The syntax says that this class is a specialization of the Grid template for all pointer types. At least that’s what it’s telling the compiler. What it’s telling you and me is that the C++ standards committee should have come up with a better syntax. Unless you’ve been working with it for a long time, it’s quite jarring.

You are providing the implementation only in cases where T is a pointer type. Note that if you instantiate a grid like this: Grid<int*> myIntGrid, then T will actually be int, not int*. That’s a bit unintuitive, but unfortunately, the way it works. Here is a code example:

image
Grid<int> myIntGrid;     // Uses the non-specialized grid
Grid<int*> psGrid(2, 2); // Uses the partial specialization for pointer types
 
int x = 3, y = 4;
psGrid.setElementAt(0, 0, &x);
psGrid.setElementAt(0, 1, &y);
psGrid.setElementAt(1, 0, &y);
psGrid.setElementAt(1, 1, &x);
 
Grid<int*> psGrid2(psGrid);
Grid<int*> psGrid3;
psGrid3 = psGrid2;
 
const Grid<int*>& psGrid4 = psGrid2;
cout << *(psGrid4.getElementAt(1, 1)) << endl;
x=6;
cout << *(psGrid4.getElementAt(1, 1)) << endl;

Code snippet from GridPartialPtrGridPtrTest.cpp

The psGrid4 grid is storing pointers, so psGrid4.getElementAt(1, 1) will return a pointer to x. Changing the value of the variable x will change the result of dereferencing the pointer returned by getElementAt(1, 1). The output of the above code should be as follows:

3
6

At this point, you’re probably wondering whether this really works. We sympathize with your skepticism. One of the authors was so surprised by this syntax when he first read about it that he didn’t believe it actually worked until he was able to try it out. If you don’t believe us, try it out yourself. Here are the method implementations. Pay close attention to the template line syntax before each method:

image
template <typename T>
Grid<T*>::Grid(size_t inWidth, size_t inHeight) :
    mWidth(inWidth), mHeight(inHeight)
{
    mCells = new T** [mWidth];
    for (size_t i = 0; i < mWidth; i++) {
        mCells[i] = new T*[mHeight];
    }
}
template <typename T>
Grid<T*>::Grid(const Grid<T*>& src)
{
    copyFrom(src);
}
template <typename T>
Grid<T*>::~Grid()
{
    // Free the old memory.
    for (size_t i = 0; i < mWidth; i++) {
        delete [] mCells[i];
    }
    delete [] mCells;
    mCells = nullptr;
}
template <typename T>
void Grid<T*>::copyFrom(const Grid<T*>& src)
{
    mWidth = src.mWidth;
    mHeight = src.mHeight;
    mCells = new T** [mWidth];
    for (size_t i = 0; i < mWidth; i++) {
        mCells[i] = new T*[mHeight];
    }
    for (size_t i = 0; i < mWidth; i++) {
        for (size_t j = 0; j < mHeight; j++) {
            mCells[i][j] = src.mCells[i][j];
        }
    }
}
template <typename T>
Grid<T*>& Grid<T*>::operator=(const Grid<T*>& rhs)
{
    // Check for self-assignment.
    if (this == &rhs) {
        return *this;
    }
    // Free the old memory.
    for (size_t i = 0; i < mWidth; i++) {
        delete [] mCells[i];
    }
    delete [] mCells;
    mCells = nullptr;
    // Copy the new memory.
    copyFrom(rhs);
    return *this;
}
template <typename T>
void Grid<T*>::setElementAt(size_t x, size_t y, T* inElem)
{
    mCells[x][y] = inElem;
}
template <typename T>
T* Grid<T*>::getElementAt(size_t x, size_t y) const
{
    return mCells[x][y];
}

Code snippet from GridPartialPtrGridPtr.h

pen.gif

For brevity, this example is not implementing any deep copying of the pointers. This would be a good exercise for the reader.

EMULATING FUNCTION PARTIAL SPECIALIZATION WITH OVERLOADING

The C++ standard does not permit partial template specialization of functions. Instead, you can overload the function with another template. The difference is subtle. Suppose that you want to write a specialization of the Find() function, presented in the previous chapter, that dereferences the pointers to use operator== directly on the objects pointed to. Following the syntax for class template partial specialization, you might be tempted to write this:

image
template <typename T>
size_t Find<T*>(T*& value, T** arr, size_t size)
{
    for (size_t i = 0; i < size; i++) {
        if (*arr[i] == *value) {
            return i; // Found it; return the index
        }
    }
    return NOT_FOUND; // failed to find it; return NOT_FOUND
}

Code snippet from FunctionTemplatePtrFindTemplatePtr.cpp

However, that syntax declares a partial specialization of the function template, which the C++ standard does not allow. The standard way to implement the behavior you want is to write a new template for Find():

image
template <typename T>
size_t Find(T*& value, T** arr, size_t size)
{
    for (size_t i = 0; i < size; i++) {
        if (*arr[i] == *value) {
            return i; // Found it; return the index
        }
    }
    return NOT_FOUND; // failed to find it; return NOT_FOUND
}

Code snippet from FunctionTemplatePtrFindTemplatePtr.cpp

The difference might seem trivial and academic, but it makes the difference between portable standard code and code that is not standard compliant.

More on Deduction

You can define in one program the original Find() template, the overloaded Find() for partial specialization on pointer types, the complete specialization for char*s, and the overloaded Find() just for char*s. The compiler will choose the appropriate version to call based on its deduction rules.

pen.gif

The compiler always chooses the “most specific” version of the function, with non-template versions being preferred over template versions.

The following code calls the specified versions of Find():

image
size_t res = NOT_FOUND;
 
int x = 3, intArr[] = {1, 2, 3, 4};
size_t sizeArr = sizeof(intArr) / sizeof(int);
res = Find(x, intArr, sizeArr);      // calls Find<int> by deduction
res = Find<int>(x, intArr, sizeArr); // calls Find<int> explicitly
 
double d1 = 5.6, dArr[] = {1.2, 3.4, 5.7, 7.5};
sizeArr = sizeof(dArr) / sizeof(double);
res = Find(d1, dArr, sizeArr);         // calls Find<double> by deduction
res = Find<double>(d1, dArr, sizeArr); // calls Find<double> explicitly
 
char* word = "two";
char* arr[] = {"one", "two", "three", "four"};
sizeArr = sizeof(arr) / sizeof(arr[0]);
res = Find<char*>(word, arr, sizeArr);// calls template specialization for char*s
res = Find(word, arr, sizeArr);       // calls overloaded Find for char*s
 
int *px = &x, *pArr[] = {&x, &x};
sizeArr = sizeof(pArr) / sizeof(pArr[0]);
res = Find(px, pArr, sizeArr);    // calls the overloaded Find for pointers
 
SpreadsheetCell c1(10), c2[] = {SpreadsheetCell(4), SpreadsheetCell(10)};
sizeArr = sizeof(c2) / sizeof(c2[0]);
res = Find(c1, c2, sizeArr);    // calls Find<SpreadsheetCell> by deduction
// calls Find<SpreadsheetCell> explicitly
res = Find<SpreadsheetCell>(c1, c2, sizeArr);
 
SpreadsheetCell *pc1 = &c1;
SpreadsheetCell *psa[] = {&c1, &c1};
sizeArr = sizeof(psa) / sizeof(psa[0]);
res = Find(pc1, psa, sizeArr);    // Calls the overloaded Find for pointers

Code snippet from FunctionTemplatePtrFindTemplatePtr.cpp

TEMPLATE RECURSION

Templates in C++ provide capabilities that go far beyond the simple classes and functions you have seen so far in this and the previous chapter. One of these capabilities is template recursion. This section first provides a motivation for template recursion, and then shows how to implement it.

cross.gif

This section employs some operator overloading features which are discussed in Chapter 18. If you skipped that chapter or are unfamiliar with the syntax for overloading operator[], consult Chapter 18 before continuing.

An N-Dimensional Grid: First Attempt

The Grid template example up to now supports only two dimensions, which limits its usefulness. What if you wanted to write a 3-D Tic-Tac-Toe game or write a math program with four-dimensional matrices? You could, of course, write a template or non-template class for each of those dimensions. However, that would repeat a lot of code. Another approach is to write only a single-dimensional grid. Then, you could create a Grid of any dimension by instantiating the Grid with another Grid as its element type. This Grid element type could itself be instantiated with a Grid as its element type, and so on. Here is the implementation of the OneDGrid class template. It’s simply a one-dimensional version of the Grid template from the earlier examples, with the addition of a resize() method, and the substitution of operator[] for setElementAt() and getElementAt(). Production code, of course, would do bounds-checking on the array access, and would throw an exception if something were amiss.

image
template <typename T>
class OneDGrid
{
    public:
        OneDGrid(size_t inSize = kDefaultSize);
        OneDGrid(const OneDGrid<T>& src);
        virtual ~OneDGrid();
        OneDGrid<T>& operator(const OneDGrid<T>& rhs);
        void resize(size_t newSize);
        T& operator[](size_t x);
        const T& operator[](size_t x) const;
        size_t getSize() const { return mSize; }
        static const size_t kDefaultSize = 10;
    protected:
        void copyFrom(const OneDGrid<T>& src);
        T* mElems;
        size_t mSize;
};
template <typename T>
OneDGrid<T>::OneDGrid(size_t inSize) : mSize(inSize)
{
    mElems = new T[mSize];
}
template <typename T>
OneDGrid<T>::OneDGrid(const OneDGrid<T>& src)
{
    copyFrom(src);
}
template <typename T>
OneDGrid<T>::~OneDGrid()
{
    delete [] mElems;
    mElems = nullptr;
}
template <typename T>
void OneDGrid<T>::copyFrom(const OneDGrid<T>& src)
{
    mSize = src.mSize;
    mElems = new T[mSize];
    for (size_t i = 0; i < mSize; i++) {
        mElems[i] = src.mElems[i];
    }
}
template <typename T>
OneDGrid<T>& OneDGrid<T>::operator=(const OneDGrid<T>& rhs)
{
    // Check for self-assignment.
    if (this == &rhs) {
        return *this;
    }
    // Free the old memory.
    delete [] mElems;
    mElems = nullptr;
    // Copy the new memory.
    copyFrom(rhs);
    return *this;
}
template <typename T>
void OneDGrid<T>::resize(size_t newSize)
{
    T* newElems = new T[newSize]; // Allocate the new array of the new size
    // Handle the new size being smaller or bigger than the old size.
    for (size_t i = 0; i < newSize && i < mSize; i++) {
        // Copy the elements from the old array to the new one.
        newElems[i] = mElems[i];
    }
    mSize = newSize; // Store the new size.
    delete [] mElems; // Free the memory for the old array.
    mElems = newElems; // Store the pointer to the new array.
}
template <typename T>
T& OneDGrid<T>::operator[](size_t x)
{
    return mElems[x];
}
template <typename T>
const T& OneDGrid<T>::operator[](size_t x) const
{
    return mElems[x];
}

Code snippet from OneDGridOneDGrid.h

With this implementation of the OneDGrid, you can create multidimensional grids like this:

image
OneDGrid<int> singleDGrid;
OneDGrid<OneDGrid<int>> twoDGrid;
OneDGrid<OneDGrid<OneDGrid<int>>> threeDGrid;
singleDGrid[3] = 5;
twoDGrid[3][3] = 5;
threeDGrid[3][3][3] = 5;

Code snippet from OneDGridOneDGridTest.cpp

This code works fine, but the declarations are messy. We can do better.

A Real N-Dimensional Grid

You can use template recursion to write a “real” N-dimensional grid because dimensionality of grids is essentially recursive. You can see that in this declaration:

OneDGrid<OneDGrid<OneDGrid<int>>> threeDGrid;

You can think of each nesting OneDGrid as a recursive step, with the OneDGrid of int as the base case. In other words, a three-dimensional grid is a single-dimensional grid of single-dimensional grids of single-dimensional grids of ints. Instead of requiring the user to do this recursion, you can write a template class that does it for you. Then, you can create N-dimensional grids like this:

NDGrid<int, 1> singleDGrid;
NDGrid<int, 2> twoDGrid;
NDGrid<int, 3> threeDGrid;

The NDGrid template class takes a type for its element and an integer specifying its “dimensionality.” The key insight here is that the element type of the NDGrid is not the element type specified in the template parameter list, but is in fact another NDGrid of dimensionality one less than the current. In other words, a three-dimensional grid is an array of two-dimensional grids; the two-dimensional grids are each arrays of one-dimensional grids.

With recursion, you need a base case. You can write a partial specialization of the NDGrid for dimensionality of 1, in which the element type is not another NDGrid, but is in fact the element type specified by the template parameter.

Here is the general NDGrid template definition, with highlights showing where it differs from the OneDGrid shown in the previous section:

image
template <typename T, size_t N>
class NDGrid
{
    public:
       NDGrid();
       NDGrid(size_t inSize);
       NDGrid(const NDGrid<T, N>& src);
       virtual ~NDGrid();
       NDGrid<T, N>& operator=(const NDGrid<T, N>& rhs);
        void resize(size_t newSize);
       NDGrid<T, N-1>& operator[](size_t x);
       const NDGrid<T, N-1>& operator[](size_t x) const;
        size_t getSize() const { return mSize; }
        static const size_t kDefaultSize = 10;
    protected:
       void copyFrom(const NDGrid<T, N>& src);
       NDGrid<T, N-1>* mElems;
        size_t mSize;
};

Code snippet from NDGridNDGrid.h

Note that mElems is a pointer to an NDGrid<T, N-1>: This is the recursive step. Also, operator[] returns a reference to the element type, which is again NDGrid<T, N-1>, not T.

Here is the template definition for the base case:

image
template <typename T>
class NDGrid<T, 1>
{
    public:
        NDGrid(size_t inSize = kDefaultSize);
       NDGrid(const NDGrid<T, 1>& src);
        virtual ~NDGrid();
       NDGrid<T, 1>& operator=(const NDGrid<T, 1>& rhs);
        void resize(size_t newSize);
       T& operator[](size_t x);
       const T& operator[](size_t x) const;
        size_t getSize() const { return mSize; }
        static const size_t kDefaultSize = 10;
    protected:
       void copyFrom(const NDGrid<T, 1>& src);
       T* mElems;
        size_t mSize;
};

Code snippet from NDGridNDGrid.h

Here the recursion ends: The element type is T, not another template instantiation.

The trickiest aspect of the implementations, other than the template recursion itself, is appropriately sizing each dimension of the array. This implementation creates the N-dimensional array with every dimension of equal size. It’s significantly more difficult to specify a separate size for each dimension. However, even with this simplification, there is still a problem: The user should have the ability to create the array with a specified size, such as 20 or 50. Thus, one constructor takes an integer size parameter. However, when you dynamically allocate the nested array of grids, you cannot pass this size value on to the grids because arrays create objects using their default constructor. Thus, you must explicitly call resize() on each grid element of the array. That code follows, with the default and one-argument constructors separated for clarity.

The base case doesn’t need to resize its elements because the elements are Ts, not grids.

Here are the implementations of the main NDGrid template, with highlights showing the differences from the OneDGrid:

image
template <typename T, size_t N>
NDGrid<T, N>::NDGrid(size_t inSize) : mSize(inSize)
{
    mElems = new NDGrid<T, N-1>[mSize];
    // Allocating the array above calls the 0-argument
    // constructor for the NDGrid<T, N-1>, which constructs
    // it with the default size. Thus, we must explicitly call
    // resize() on each of the elements.
    for (size_t i = 0; i < mSize; i++) {
        mElems[i].resize(inSize);
    }
}
template <typename T, size_t N>
NDGrid<T, N>::NDGrid() : mSize(kDefaultSize)
{
    mElems = new NDGrid<T, N-1>[mSize];
}
template <typename T, size_t N>
NDGrid<T, N>::NDGrid(const NDGrid<T, N>& src)
{
    copyFrom(src);
}
template <typename T, size_t N>
NDGrid<T, N>::~NDGrid()
{
    delete [] mElems;
    mElems = nullptr;
}
template <typename T, size_t N>
void NDGrid<T, N>::copyFrom(const NDGrid<T, N>& src)
{
    mSize = src.mSize;
    mElems = new NDGrid<T, N-1>[mSize];
    for (size_t i = 0; i < mSize; i++) {
        mElems[i] = src.mElems[i];
    }
}
template <typename T, size_t N>
NDGrid<T, N>& NDGrid<T, N>::operator=(const NDGrid<T, N>& rhs)
{
    // Check for self-assignment.
    if (this == &rhs) {
        return *this;
    }
    // Free the old memory.
    delete [] mElems;
    mElems = nullptr;
    // Copy the new memory.
    copyFrom(rhs);
    return *this;
}
template <typename T, size_t N>
void NDGrid<T, N>::resize(size_t newSize)
{
    // Allocate the new array with the new size.
    NDGrid<T, N - 1>* newElems = new NDGrid<T, N - 1>[newSize];
    // Copy all the elements, handling the cases where newSize is
    // larger than mSize and smaller than mSize.
    for (size_t i = 0; i < newSize && i < mSize; i++) {
        newElems[i] = mElems[i];
        // Resize the nested Grid elements recursively.
        newElems[i].resize(newSize);
    }
    // Store the new size and pointer to the new array.
    // Free the memory for the old array first.
    mSize = newSize;
    delete [] mElems;
    mElems = newElems;
}
template <typename T, size_t N>
NDGrid<T, N-1>& NDGrid<T, N>::operator[](size_t x)
{
    return mElems[x];
}
template <typename T, size_t N>
const NDGrid<T, N-1>& NDGrid<T, N>::operator[](size_t x) const
{
    return mElems[x];
}

Code snippet from NDGridNDGrid.h

Here are the implementations of the partial specialization (base case). Note that you must rewrite a lot of the code because you don’t inherit any implementations with specializations. Highlights show the differences from the non-specialized NDGrid:

image
template <typename T>
NDGrid<T, 1>::NDGrid(size_t inSize) : mSize(inSize)
{
    mElems = new T[mSize];
}
template <typename T>
NDGrid<T, 1>::NDGrid(const NDGrid<T, 1>& src)
{
    copyFrom(src);
}
template <typename T>
NDGrid<T, 1>::~NDGrid()
{
    delete [] mElems;
    mElems = nullptr;
}
template <typename T>
void NDGrid<T, 1>::copyFrom(const NDGrid<T, 1>& src)
{
    mSize = src.mSize;
    mElems = new T[mSize];
    for (size_t i = 0; i < mSize; i++) {
        mElems[i] = src.mElems[i];
    }
}
template <typename T>
NDGrid<T, 1>& NDGrid<T, 1>::operator=(const NDGrid<T, 1>& rhs)
{
    // Check for self-assignment.
    if (this == &rhs) {
        return *this;
    }
    // Free the old memory.
    delete [] mElems;
    mElems = nullptr;
    // Copy the new memory.
    copyFrom(rhs);
    return *this;
}
template <typename T>
void NDGrid<T, 1>::resize(size_t newSize)
{
    T* newElems = new T[newSize];
    for (size_t i = 0; i < newSize && i < mSize; i++) {
        newElems[i] = mElems[i];
        // Don't need to resize recursively, because this is the base case.
    }
    mSize = newSize;
    delete [] mElems;
    mElems = newElems;
}
template <typename T>
T& NDGrid<T, 1>::operator[](size_t x)
{
    return mElems[x];
}
template <typename T>
const T& NDGrid<T, 1>::operator[](size_t x) const
{
    return mElems[x];
}

Code snippet from NDGridNDGrid.h

Now, you can write code like this:

image
NDGrid<int, 3> my3DGrid;
my3DGrid[2][1][2] = 5;
my3DGrid[1][1][1] = 5;
cout << my3DGrid[2][1][2] << endl;

Code snippet from NDGridNDGridTest.cpp

imageTYPE INFERENCE

Type inference is new in C++11 and allows the compiler to automatically deduce the exact type of an expression. There are two new keywords for type inference: auto and decltype. Type inference turns out to be very useful in combination with templates. This section goes into more detail on their use in a template context.

The auto Keyword

The new auto keyword has two completely different meanings. The first meaning is to tell the compiler to automatically deduce the type of a variable at compile time. The following line shows the simplest use of the auto keyword in that context:

auto x = 123;    // x will be of type int

In this example you don’t win much by typing auto instead of int; however, it becomes useful for more complicated types. Suppose you have a map that maps an int to a vector of strings:

std::map<int, std::vector<std::string>> m;

Before C++11, if you wanted a constant iterator to the beginning of this map, you had to type the following:

std::map<int, std::vector<std::string>>::const_iterator citer = m.begin();

With the auto keyword it becomes:

auto citer = m.cbegin();

The second use of the auto keyword is when using the new C++11 alternative function syntax, which is mentioned in Chapter 9. Basically, it allows you to put the return type at the end of the function prototype instead of at the beginning. For example, take the following function:

int func(int i)
{
    return i + 2;
}

This can be rewritten by using the alternative function syntax as follows:

auto func(int i) -> int
{
    return i + 2;
}

The auto keyword here has a completely different meaning. It states that this function prototype is using the alternative function syntax. If you look at the preceding example you might think that this alternative function syntax doesn’t really add any new value to the language. However, the new syntax becomes important in the context of templates together with the new decltype keyword, as you will see in the next sections.

The decltype Keyword

The decltype keyword takes an expression as argument, and computes the type of that expression. For example:

int x = 123;
decltype(x) y = 456;

In this example, the compiler will make y of type int because that’s the type of x. Just like the auto keyword for the alternative function syntax, the decltype keyword doesn’t seem to add much value on first sight. However, in the context of templates, auto and decltype become pretty powerful.

auto and decltype with Templates

The use of the auto and decltype keywords in combination with templates is best illustrated with an example. The following example defines two classes: MyInt and MyString. These are simple wrappers for an int and a std::string, respectively. Their constructors accept a single value used for initialization. Both classes also have an operator+ as member method. Here is the header file:

image
#include <string>
// Forward class declarations
class MyInt;
class MyString;
 
class MyInt
{
    public:
        MyInt(int i) : m_i(i) {}
        MyInt operator+(const MyString& rhs) const;
        int getInt() const { return m_i; }
    protected:
        int m_i;
};
 
class MyString
{
    public:
        MyString(std::string str) : m_str(str) {}
        MyString operator+(const MyInt& rhs) const;
        const std::string& getString() const { return m_str; }
    protected:
        std::string m_str;
};

Code snippet from TypeInferenceTypeInference.h

The implementation is as follows. This code uses two other new features of C++11: stoi() and to_string(), both of them are discussed in the “Numeric Conversions” section in Chapter 14:

image
MyInt MyInt::operator+(const MyString& rhs) const
{
    return m_i + stoi(rhs.getString());
}
MyString MyString::operator+(const MyInt& rhs) const
{
    string str = m_str;
    str.append(to_string(rhs.getInt()));
    return str;
}

Code snippet from TypeInferenceTypeInference.cpp

With this implementation you will get a different result depending on the order of the arguments to the addition operator. For example:

image
MyInt i(4);
MyString str("5");
MyInt a = i + str;
MyString b = str + i;

Code snippet from TypeInferenceTypeInference.cpp

In this case, the type of variable a should be MyInt and the type of variable b should be MyString.

Imagine that you want to write a function template to perform the addition. You can write the following:

image
template<typename T1, typename T2, typename Result>
Result DoAddition(const T1& t1, const T2& t2)
{
    return t1 + t2;
}

Code snippet from TypeInferenceTypeInference.cpp

As you can see, it requires you to specify three template parameters: the type of the first operand, the type of the second operand, and the type of the result of performing the addition. You can call this function template as follows:

image
auto c = DoAddition<MyInt, MyString, MyInt>(i, str);

Code snippet from TypeInferenceTypeInference.cpp

This is obviously not that elegant because you need to manually specify the type of the return value. After reading about the decltype keyword, you might want to try to fix this issue as follows:

template<typename T1, typename T2>
decltype(t1 + t2) DoAddition(const T1& t1, const T2& t2)
{
    return t1 + t2;
}

However, this will not work because at the time of parsing the decltype keyword, the compiler doesn’t know t1 and t2 yet. They become known later in the function prototype. The correct solution is to combine the alternative function syntax with the decltype keyword as shown in the following implementation:

image
template<typename T1, typename T2>
auto DoAddition2(const T1& t1, const T2& t2) -> decltype(t1 + t2)
{
    return t1 + t2;
}

Code snippet from TypeInferenceTypeInference.cpp

With this implementation you can call DoAddition2() as follows:

image
auto d = DoAddition2(i, str);
auto e = DoAddition2(str, i);

Code snippet from TypeInferenceTypeInference.cpp

You can see that you need not specify any function template parameters anymore because the compiler will deduce the two template parameter types based on the arguments given to DoAddition2(), and will automatically calculate the type of the return value. In this example, d will be of type MyInt and e will be of type MyString.

imageVARIADIC TEMPLATES

Normal templates can take only a fixed number of template parameters. For example, the following code defines a template that requires three template parameters:

template<typename T1, typename T2, typename T3>
class MyTemplate { }

C++11 introduces the notion of variadic templates. These are templates that can take a variable number of template parameters. For example, the following code defines a template that can accept any number of template parameters, using a parameter pack called Types:

template<typename... Types>
class MyVariadicTemplate { }
pen.gif

The three dots behind typename are not an error. This is the syntax for variadic templates. You are allowed to put spaces before and after the three dots. Thus, the previous variadic template can also be written as follows:

template<typename ... Types>class MyVariadicTemplate { }

You can instantiate the MyVariadicTemplate with any number of types. For example:

MyVariadicTemplate<int> temp1;
MyVariadicTemplate<string, double, list<int>> temp2;

The MyVariadicTemplate can even be instantiated with zero template arguments:

MyVariadicTemplate<> temp3;

To avoid instantiating a variadic template with zero template arguments, you can write your template as follows:

template<typename T1, typename... Types>
class MyVariadicTemplate { }

With this new definition, trying to instantiate MyVariadicTemplate with zero template arguments will result in a compiler error:

error: wrong number of template arguments (0, should be 1 or more)

It is not possible to directly iterate over the different arguments given to a variadic template. The only way you can do this is with the aid of template recursion. The following sections show two examples on how to use variadic templates.

Type-Safe Variable-Length Argument Lists

Variadic templates allow you to create type-safe variable-length argument lists. The following example defines a function called processValues() that uses this feature to allowing it to accept a variable number of arguments with different types in a type-safe way. The function processValues() will process each value in the variable-length argument list and will execute a function called handleValue() for each single argument. This means that you have to write a handleValue() function for each type that you want to handle; int, double and string in the following example:

image
void handleValue(int value) { cout << "Integer: " << value << endl; }
void handleValue(double value) { cout << "Double: " << value << endl; }
void handleValue(const char* value) { cout << "String: " << value << endl; }
template<typename T>
void processValues(T arg) // Base case
{
    handleValue(arg);
}
template<typename T1, typename... Tn>
void processValues(T1 arg1, Tn... args)
{
    processValues(arg1);
    processValues(args...);
}

Code snippet from VarArgsVarArgsWithVariadicTemplates.cpp

What the preceding example also demonstrates is the double use of the triple dots ... operator. This operator appears in two places. First, it is used behind typename to denote that this parameter can accept a variable number of arguments. The official name for typename... Tn is a parameter pack. The second use of the ... operator is behind args. In this case, the operator will unpack/expand the parameter pack args. Take the following line from the preceding example:

processValues(args...);

This line will unpack or expand the args parameter pack into its separate arguments, separated by commas, and then call the processValues() function with those expanded arguments. The template always requires at least one template parameter, T1. The act of recursively calling processValues() with args... is that on each call there will be one template parameter less.

Since the implementation of the processValues() function is recursive, you need to have a way to stop the recursion. This is done by implementing a partial specialization of the processValues() template function, which accepts just a single template argument.

You can test the processValues() variadic template as follows:

image
processValues(1, 2, 3.56, "test", 1.1f);

Code snippet from VarArgsVarArgsWithVariadicTemplates.cpp

The recursive calls generated by this example are:

processValues(1, 2, 3.56, "test", 1.1f);
  processValues(1);
    handleValue(1);
  processValues(2, 3.56, "test", 1.1f);
    processValues(2);
      handleValue(2);
    processValues(3.56, "test", 1.1f);
      processValues(3.56);
        handleValue(3.56);
      processValues("test", 1.1f);
        processValues("test");
          handleValue("test");
        processValues(1.1f);
          handleValue(1.1f);

It is important to remember that this method of variable-length argument lists is fully type-safe. The processValues() function will automatically call the correct handleValue() overload based on the actual type. Automatic casting can happen as usual in C++. For example, the 1.1f in the preceding example is of type float. The processValues() function will call handleValue(double value) because conversion from float to double is without any loss. However, the compiler will issue an error when you call processValues() with an argument of a certain type for which there is no handleValue() function defined.

There is one small problem with the preceding implementation. Since it’s a recursive implementation, the parameters will be copied for each recursive call to processValues(). This can become costly depending on the type of the arguments. You might think that you can avoid this copying by passing references to the processValues() function instead of using pass-by-value. Unfortunately that also means that you cannot call processValues() with constant values anymore, because a reference to a constant value is not allowed.

To use references and still allow constant values, you can leverage another C++11 feature called rvalue references, which are discussed in Chapter 9. Doing that results in the following implementation:

image
template<typename T>
void processValuesRValueRefs(T&& arg)
{
    handleValue(arg);
}
template<typename T1, typename... Tn>
void processValuesRValueRefs(T1&& arg1, Tn&&... args)
{
    processValuesRValueRefs(arg1);
    processValuesRValueRefs(args...);
}

Code snippet from VarArgsVarArgsWithVariadicTemplates.cpp

Inside the body of a function using a parameter pack you can retrieve the number of arguments in the pack as follows:

int numOfArgs = sizeof...(args);

A practical example of using variadic templates is to write a secure and type-safe version of printf().

Variable Number of Mix-In Classes

Parameter packs can be used almost everywhere. For example, the following code uses a parameter pack to define a variable number of mix-in classes for the MyClass class. Chapter 3 discusses the concept of mix-in classes.

image
class Mixin1
{
    public:
        Mixin1(int i) : m_i(i) {}
        virtual void Mixin1Func() {cout << "Mixin1: " << m_i << endl;}
    protected:
        int m_i;
};
class Mixin2
{
    public:
        Mixin2(int i) : m_i(i) {}
        virtual void Mixin2Func() {cout << "Mixin2: " << m_i << endl;}
    protected:
        int m_i;
};
template<typename... Mixins>
class MyClass : public Mixins...
{
    public:
        MyClass(const Mixins&... mixins) : Mixins(mixins)... {}
};

Code snippet from VariadicMixinsVariadicMixins.cpp

This code first defines two mix-in classes Mixin1 and Mixin2. They are kept pretty simple for this example. Their constructor accepts an integer, which is stored, and they have a function to print information about that specific instance of the class. The MyClass variadic template uses a parameter pack typename... Mixins to accept a variable number of mix-in classes. The class then inherits from all those mix-in classes and the constructor accepts the same number of arguments to initialize each inherited mix-in class. The class can be used as follows:

image
MyClass<Mixin1, Mixin2> a(Mixin1(11), Mixin2(22));
a.Mixin1Func();
a.Mixin2Func();
 
MyClass<Mixin1> b(Mixin1(33));
b.Mixin1Func();
//b.Mixin2Func();    // Error: does not compile.

Code snippet from VariadicMixinsVariadicMixins.cpp

When you try to call Mixin2Func() on b you will get a compiler error because b is not inheriting from the Mixin2 class. The output of this program is as follows:

Mixin1: 11
Mixin2: 22
Mixin1: 33

METAPROGRAMMING

This section touches on template metaprogramming. It is a very complicated subject and there are books written about it explaining all the little details. This book doesn’t have the space to go into all the details of metaprogramming. Instead, this section explains the most important concepts, with the aid of a couple of examples.

The goal of template metaprogramming is to perform some computation at compile time instead of at run time. It is basically a mini programming language on top of C++. The following section starts the discussion with a simple demonstration that calculates the factorial of a number at compile time and makes the result available as a simple constant at run time.

Factorial at Compile Time

Template metaprogramming allows you to perform calculations at compile time instead of at run time. The following code is a small example that calculates the factorial of a number at compile time:

image
template<long long f>
class Factorial
{
    public:
        static const long long val = (f*Factorial<f-1>::val);
};
template<>
class Factorial<1>
{
    public:
        static const long long val = 1;
};
int main()
{
    cout << Factorial<6>::val << endl;
    return 0;
}

Code snippet from FactorialFactorial.cpp

This will calculate the factorial of 6, mathematically written as 6!, which is 1×2×3×4×5×6 or 720. The code is using template recursion as explained earlier in this chapter, which requires the general recursive template and the base template to stop the recursion.

pen.gif

It is important to remember that the factorial calculations are happening at compile time. At run time you access the compile time calculated value through ::val, which is just a constant value.

Loop Unrolling

A second example of template metaprogramming is to unroll loops at compile time instead of executing the loop at run time. Take a look at the following example:

image
template<int i, typename FuncType>
class Loop
{
    public:
        static inline void Do(FuncType func) {
            Loop<i-1, FuncType>::Do(func);
            func(i);
        }
};
template<typename FuncType>
class Loop<-1, FuncType>
{
    public:
        static inline void Do(FuncType func) { }
};

Code snippet from LoopUnrollingLoopUnrolling.cpp

This example uses template recursion because it needs to do something in a loop at compile time. For template recursion you need the recursive implementation of the template and a base template that will stop the recursion. On each recursion, the Loop template will instantiate itself with i-1. When it hits -1, the base template is used, which stops the recursion. The Loop template can be used as follows:

image
void DoWork(int i) { cout << "DoWork(" << i << ")" << endl; }
int main()
{
    Loop<3, function<void(int)>>::Do(DoWork);
}

Code snippet from LoopUnrollingLoopUnrolling.cpp

This code will cause the compiler to unroll the loop and will call the function DoWork() four times in a row (0-3). Note that this example is using the C++11 std::function feature, which is explained in Chapter 16. The output of the program is:

DoWork(0)
DoWork(1)
DoWork(2)
DoWork(3)

This already looks interesting, but we can do better, and make the call even easier to write by using decltype:

image
Loop<3, decltype(DoWork)>::Do(DoWork);

Code snippet from LoopUnrollingLoopUnrolling.cpp

This code will output exactly the same as the first version. However, here you ask the compiler to deduce the type of the DoWork() function automatically by using the decltype keyword so that you don’t need to write the exact type yourself.

To further demonstrate the power of the new C++11 features in combination with template metaprogramming, take the following call for the Loop template:

image
double DoWork2(string str, int i)
{
    cout << "DoWork2(" << str << ", " << i << ")" << endl;
    return 0.0;
}
int main()
{
    auto a = bind(DoWork2, "TestStr", placeholders::_1);
    Loop<2, decltype(a)>::Do(a);
}

Code snippet from LoopUnrollingLoopUnrolling.cpp

This is using quite a number of C++11 features. The code first implements a function that accepts a string and an int and returns a double. The main() function uses std::bind() to bind the first parameter of DoWork2() to a fixed string, "TestStr". See Chapter 13 for details on std::bind(). The result of std::bind() is assigned to a, and because of the auto keyword, the compiler will automatically deduce the type of a. The code then instantiates the Loop template and uses decltype to let the compiler deduce the type of a and use it as the second template parameter. If you compile and run this code, the output should be as follows:

DoWork2(TestStr, 0)
DoWork2(TestStr, 1)
DoWork2(TestStr, 2)

You can go one step further and remove the need to specify the type of the function as the second parameter for the Loop template altogether by defining a function template that will deduce the type automatically:

image
template<int i, typename FuncType>
void loopFunc(FuncType f)
{
    Loop<i, FuncType>::Do(f);
}
int main()
{
    loopFunc<2>(bind(DoWork2, "TestStr", placeholders::_1));
}

Code snippet from LoopUnrollingLoopUnrolling.cpp

The output will be the same as before. As you can see in the main() function, you don’t need to specify the type of the DoWork2() function anymore. The loopFunc() function template will automatically deduce this type and use it to instantiate the Loop template.

imagePrinting Tuples

This example will use template metaprogramming to print the individual elements of a C++11 std::tuple. Tuples are explained in Chapter 16. They allow you to store any number of values, each with its own specific type. A tuple has a fixed size and fixed value types, determined at compile time. However, tuples don’t have any built-in mechanism to iterate over their elements. The following example shows how you could use template metaprogramming to iterate over the elements of a tuple at compile time:

image
template<int n, typename TupleType>
class tuple_print
{
    public:
        tuple_print(TupleType t) {
            tuple_print<n-1, TupleType> tp(t);
            cout << get<n-1>(t) << endl;
        }
};
template<typename TupleType>
class tuple_print<0, TupleType>
{
    public:
        tuple_print(TupleType t) {}
};
int main()
{
    typedef tuple<int, string, bool> MyTuple;
    MyTuple t1(16, "Test", true);
    tuple_print<tuple_size<MyTuple>::value, MyTuple> tp(t1);
}

Code snippet from PrintTuplePrintTuple.cpp

Like it is often the case with template metaprogramming, this example is again using template recursion. There is a tuple_print template class that accepts a size integer and the type of the tuple. It then recursively instantiates itself in the constructor and decrements the size on every call. A partial specialization of the tuple_print class for a zero sized tuple stops the recursion. The main() function shows how this tuple_print template class could be used.

If you look at the main() function, you can see that the line to use the tuple_print template looks a bit complicated because it requires the size of the tuple and the exact type of the tuple as template arguments. This can be simplified a lot by introducing a template helper function that will automatically deduce the template parameters. The simplified implementation is as follows:

image
template<int n, typename TupleType>
class tuple_print_helper
{
    public:
        tuple_print_helper(TupleType t) {
           tuple_print_helper<n-1, TupleType> tp(t);
            cout << get<n-1>(t) << endl;
        }
};
template<typename TupleType>
class tuple_print_helper<0, TupleType>
{
    public:
        tuple_print_helper(TupleType t) {}
};
template<typename T>
void tuple_print(T t)
{
    tuple_print_helper<tuple_size<T>::value, T> tph(t);
}
int main()
{
    auto t1 = make_tuple(167, "Testing", false, 2.3);
    tuple_print(t1);
}

Code snippet from PrintTuplePrintTupleSimplified.cpp

The first change made here is to rename the original tuple_print template class to tuple_print_helper. The code then implements a small function template called tuple_print(), which accepts the type of the tuple as a template argument and accepts the tuple itself as a function argument. The body of that function instantiates the tuple_print_helper template class. The main() function shows how to use this simplified version. Since you don’t need to know the exact type of the tuple yourself anymore, you can use the recommended make_tuple() together with the auto keyword to avoid having to write the tuple type yourself. The call to the tuple_print() function template is very simple:

tuple_print(t1);

You don’t need to specify the function template argument because the compiler can deduce this automatically from the supplied argument.

imageType Traits

Type traits allow you to make decisions based on types at compile time. For example, you can write a template that requires a type that is derived from a certain type, or a type that is convertible to a certain type, or a type that is integral, and so on. The standard defines several helper classes for this. The following list gives a few examples of the available type traits-related classes in the C++11 standard. See the Reference resource on the website (www.wrox.com) for a complete list.

  • Primary type categories
    • is_void
    • is_integral
    • is_floating_point
    • is_pointer
    • . . .
  • Composited type categories
    • is_reference
    • is_object
    • is_scalar
    • . . .
  • Type properties
    • is_const
    • is_literal_type
    • is_polymorphic
    • is_unsigned
    • is_constructible
    • is_copy_constructible
    • is_move_constructible
    • is_assignable
    • . . .
  • Type relations
    • is_same
    • is_base_of
    • is_convertible
  • const-volatile modifications
    • remove_const
    • add_const
    • . . .
  • Reference modifications
    • remove_reference
    • add_lvalue_reference
    • add_rvalue_reference
  • Sign modifications
    • make_signed
    • make_unsigned
  • Other transformations
    • enable_if
    • conditional
    • . . .

Type traits is a pretty advanced and complicated feature. By just looking at the preceding list, which is already a shortened version of the list in the standard itself, it is clear that this book cannot explain all details about type traits. This section explains just a couple of use cases to show you how type traits could be used.

Using Type Categories

Type traits requires the <type_traits> header file. Before an example can be given for a template using type traits, you first need to know a bit more on how classes like is_integral work.

The standard defines an integral_constant class that looks as follows:

template <class T, T v>
struct integral_constant {
    static constexpr T value = v;
    typedef T value_type;
    typedef integral_constant<T,v> type;
    constexpr operator value_type() { return value; }
};
typedef integral_constant<bool, true> true_type;
typedef integral_constant<bool, false> false_type;

What this defines is two types: true_type and false_type. When you call true_type::value you will get the value true and when you call false_type::value you will get the value false. You can also call true_type::type, which will return the type of true_type. The same holds for false_type. Classes like is_integral and is_class inherit from integral_constant. For example, is_integral can be specialized for type bool as follows:

template<> struct is_integral<bool> : true_type { };

This allows you to write is_integral<bool>::value, which will return the value true. Note that you don’t need to write these specializations yourself; they are part of the standard library.

The following code shows the simplest example of how type categories can be used:

image
if (is_integral<int>::value) {
    cout << "int is integral" << endl;
} else {
    cout << "int is not integral" << endl;
}
if (is_class<string>::value) {
    cout << "string is a class" << endl;
} else {
    cout << "string is not a class" << endl;
}

Code snippet from TypeTraitsasic.cpp

This example is using is_integral to check whether int is an integral type or not, and uses is_class to check whether string is a class or not. The output is as follows:

int is integral
string is a class

Of course, you will likely never use type traits in this way. They become more useful in combination with templates to generate code based on some properties of a type. The following template example demonstrates this:

image
template<typename T>
void process_helper(const T& t, true_type)
{
    cout << t << " is an integral type." << endl;
}
template<typename T>
void process_helper(const T& t, false_type)
{
    cout << t << " is a non-integral type." << endl;
}
template<typename T>
void process(const T& t)
{
    process_helper(t, typename is_integral<T>::type());
}
int main()
{
    process(123);
    process(2.2);
    process(string("Test"));
    return 0;
}

Code snippet from TypeTraitsis_integral.cpp

The output of this example is as follows:

123 is an integral type.
2.2 is a non-integral type.
Test is a non-integral type.

The preceding code defines an overloaded function template process_helper() that accepts a type as template argument. The first argument to this function is a value and the second argument is either an instance of true_type or false_type. The process() function template accepts a single argument and will call the process_helper() function with the second parameter defined as follows:

typename is_integral<T>::type()

This uses is_integral to see if T is an integral type. Calling ::type will return the resulting integral_constant, which can be a true_type or a false_type. The process_helper() function needs an instance of true_type or false_type as second parameter, so that is the reason for the two empty brackets behind ::type. Note that the two overloaded process_helper() functions use nameless parameters of type true_type or false_type. They are nameless because they don’t use those parameters inside their function body. If you don’t like this syntax, you might as well write the following:

void process_helper(const T& t, true_type tt)

However, because tt will not be used in the body of the function, this will most likely trigger a warning from your compiler saying that tt is unused.

Using Type Relations

There are three type relations available: is_same, is_base_of and is_convertible. They all work similarly. This section gives an example of how to use is_same.

The following example defines a same_helper() function template. It accepts two constant references and one Boolean value. The function prints the two values followed by a specific string depending on the value of the Boolean parameter. The same() function template uses the is_same type traits feature to figure out whether the two given arguments are of the same type or not and then calls the same_helper() function. It uses ::value because the same_helper() function requires a Boolean value true or false. Using the same() function template is very easy as is shown in the main() function:

image
template<typename T1, typename T2>
void same_helper(const T1& t1, const T2& t2, bool b)
{
    cout << "'" << t1 << "' and '" << t2 << "' are ";
    cout << (b ? "the same types." : "different types.") << endl;
}
template<typename T1, typename T2>
void same(const T1& t1, const T2& t2)
{
    same_helper(t1, t2, is_same<T1, T2>::value);
}
int main()
{
    same(1, 32);
    same(1, 3.01);
    same(3.01, string("Test"));
}

Code snippet from TypeTraitsis_same.cpp

The output should be as follows:

'1' and '32' are the same types.
'1' and '3.01' are different types
'3.01' and 'Test' are different types

Using enable_if

First, a little warning before continuing with enable_if. Using enable_if requires knowledge of a feature called Substitution Failure Is Not An Error (SFINAE), a complicated and obscure feature of C++. This section explains the basics of SFINAE with an example.

The previous example with the same() and same_helper() function templates can be rewritten into one overloaded check_type() function template by using the enable_if type traits transformation as follows:

image
template<typename T1, typename T2>
void check_type(const T1& t1, const T2& t2,
    typename enable_if<is_same<T1, T2>::value>::type* p = nullptr)
{
    cout << "'" << t1 << "' and '" << t2 << "' ";
    cout << "are the same types." << endl;
}
template<typename T1, typename T2>
void check_type(const T1& t1, const T2& t2,
    typename enable_if<!is_same<T1, T2>::value>::type* p = nullptr)
{
    cout << "'" << t1 << "' and '" << t2 << "' ";
    cout << "are different types." << endl;
}
int main()
{
    check_type(1, 32);
    check_type(1, 3.01);
    check_type(3.01, string("Test"));
} 

Code snippet from TypeTraitsenable_if.cpp

The output will be:

'1' and '32' are the same types.
'1' and '3.01' are different types.
'3.01' and 'Test' are different types.

This example defines one function called check_type(), which is overloaded on its third parameter. The third parameter for the first overload looks as follows:

typename enable_if<is_same<T1, T2>::value>::type* p = nullptr

It first uses is_same to check whether the two types are the same or not and gets the value of that result with ::value. This value is given to enable_if, and ::type is used to get the resulting type. When the argument to enable_if is true, calling ::type will return some valid type. It is not important to know what exacty this returned type is; it’s only important to know that it will return some valid type. However, when the argument to enable_if is false, the result will not have a wrapped type so calling ::type will fail. This is where SFINAE comes into play.

When the compiler starts to compile the first line of the main() function, it tries to find a function check_type() that accepts two integer values. It will find the first check_type() function template overload in the source code and will deduce that it can use an instance of this by making T1 and T2 both integers. It will then try to process the third parameter. Since both types are integers and thus the same, is_same<T1, T2>::value will return true, which will cause enable_if<true>::type to return some valid type. With this instantiation, everything is fine and the compiler can use that version of check_type().

However, when the compiler tries to compile the second line in the main() function, it will again try to find a suitable check_type() function. It starts with the first overload and decides it can use that overload by setting T1 to type integer and T2 to type double. It will then try to process the third parameter. This time, T1 and T2 are different types, which means that is_same<T1, T2>::value will return false. Because of this, enable_if<false> will not have a wrapped type and calling enable_if<false>::type will fail. The compiler will notice this error but will not yet generate a real compilation error because of SFINAE. The compiler will gracefully backtrack and try to find another check_type() function. In this case the second check_type() overload will work out perfectly because !is_same<T1, T2>::value will be true and thus enable_if<true>::type will be some valid type.

cross.gif

As mentioned before, relying on SFINAE can become tricky and complicated. It is recommended to use it judiciously.

Conclusion

As you have seen in this section, template metaprogramming can be a very powerful tool, but it can also get quite complicated. One problem with template metaprogramming, not yet mentioned before, is that everything happens at compile time so you cannot use a debugger to pinpoint a problem. If you decide to use template metaprogramming in your code, make sure you write good comments to explain exactly what is going on and why you are doing something a certain way. If you don’t properly document your template metaprogramming code, it might be very difficult for someone else to understand your code, and it might even make it difficult for yourself to understand your own code in the future.

SUMMARY

This and the previous chapter show you how to use templates for generic programming and template metaprogramming for compile time computations. We hope that you gained an appreciation for the power and capabilities of these features, and an idea of how you could apply these concepts to your own code. Don’t worry if you didn’t understand all the syntax, or follow all the examples, on your first reading. The concepts can be difficult to grasp when you are first exposed to them, and the syntax is tricky whenever you want to write somewhat complicated templates. When you actually sit down to write a template class or function, you can consult this chapter and the previous one for a reference on the proper syntax.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset