Keywords

These keywords were added by machine and not by the authors. This process is experimental and the keywords may be updated as the learning algorithm improves.

You learned about templates that the compiler uses to create functions back in Chapter 8; this chapter is about templates the compiler can use to create classes. Class templates are a powerful mechanism for generating new class types automatically. A significant portion of the Standard Library is built entirely on the ability to define templates, particularly the Standard Template Library, which includes many class and function templates.

By the end of this chapter, you will have learned:

  • What a class template is and how it is defined

  • What an instance of a class template is, and how it is created

  • How to define templates for member functions of a class template outside the class template definition

  • How type parameters differ from non-type parameters

  • How static members of a class template are initialized

  • What a partial specialization of a class template is and how it is defined

  • How a class can be nested inside a class template

Understanding Class Templates

Class templates are based on the same idea as the function templates. A class template is a parameterized type — a recipe for creating a family of class types, using one or more parameters. The argument for each parameter is typically (but not always) a type. When you define a variable that has a type specified by a class template, the compiler uses the template to create a definition of a class using the template arguments that you use in the type specification. You can use a class template to generate any number of different classes. It’s important to keep in mind that a class template is not a class, but just a recipe for creating classes, because this is the reason for many of the constraints on how you define class templates.

A class template has a name, just like a regular class, and one or more parameters. A class template must be unique within a namespace, so you can’t have another template with the same name and parameter list in the namespace in which the template is defined. A class definition is generated from a class template when you supply an argument for each of the template’s parameters. This is illustrated in Figure 16-1.

Figure 16-1.
figure 1figure 1

Instantiating a template

Each class that the compiler generates from a template is called an instance of the template. When you define a variable using a template type for the first time, you create an instance of the template; variables of the same type defined subsequently will use the first instance created. You can also cause instances of a class template to be created without defining a variable. The compiler does not process a class template in a source file in any way if the template is not used to generate a class.

There are many applications for class templates but they are perhaps most commonly used to define container classes. These are classes that can contain sets of objects of a given type, organized in a particular way. In a container class the organization of the data is independent of the type of objects stored. Of course, you already have experience of instantiating and using std::vector and std::array class templates that define containers where the data is organized sequentially.

Defining Class Templates

Class template definitions tend to look more complicated than they really are, largely because of the notation used to define them and the parameters sprinkled around the statements in their definitions. Class template definitions are similar to those of ordinary classes, but like so many things, the devil is in the details. A class template is prefixed by the template keyword followed by the parameters for the template between angled brackets. The template class definition consists of the class keyword followed by the class template name with the body of the definition between braces. Just like a regular class, the whole definition ends with a semicolon. The general form of a class template looks like this:

template <template parameter list>

class ClassName

{

// Template class definition...

};

ClassName is the name of this template. You write the code for the body of the template just as you’d write the body of an ordinary class, except that some of the member declarations and definitions will be in terms of the template parameters that appear between the angled brackets. To create a class from a template, you must specify arguments for every parameter in the list. This differs from a function template where most of the time the compiler can deduce the template arguments from the context.

Template Parameters

A template parameter list can contain any number of parameters that can be of two kinds — type parameters and non-type parameters. The argument corresponding to a type parameter is always a type, such as int, or std::string, or Box; the argument for a non-type parameter can be a literal of an integral type such as 200, an integral constant expression, a pointer or reference to an object, a pointer to a function or a pointer that is null. Type parameters are much more commonly used than non-type parameters, so I’ll explain these first and defer discussion of non-type parameters until later in this chapter.

Note

There’s a third possibility for class template parameters. A parameter can also be a template where the argument must be an instance of a class template. A detailed discussion of this possibility is a little too advanced for this book.

Figure 16-2 illustrates the options for type parameters. You can write type parameters using the class keyword or the typename keyword preceding the parameter name (typename T in Figure 16-2 for example). I prefer to use typename because class tends to connote a class type and the type argument doesn’t have to be a class type. T is often used as a type parameter name (or T1, T2, and so on when there are several type parameters) because it’s concise and easy to identify in the template definition but you can use whatever name you want.

Figure 16-2.
figure 2figure 2

Class template parameters

A Simple Class Template

Let’s take an example of a class template for arrays that will do bounds checking on index values to make sure that they are legal. The Standard Library provides a comprehensive implementation of an array template but building a limited array template is an effective basis from which you can learn how templates work. You already have clear idea of how arrays work so you can concentrate on the template specifics.

This template just has a single type parameter, so in outline, its definition will be:

template <typename T>

class Array

{

// Definition of the template...

};

The Array template has just one type parameter, T. You can tell that it’s a type parameter because it’s preceded by the keyword typename. Whatever is “plugged in” for the parameter when you instantiate the template — int, double*, string, or whatever — determines the type of the elements stored in an object of the resultant class. The definition in the body of the template will be much the same as a class definition, with data and function members that are specified as public, protected, or private, and it will typically have constructors and a destructor. You can use T to define data members or to specify the parameters or return types for function members, either by itself or in types such as T* or T&&. You can use the template name with its parameter list — Array<T>, in this case — as a type name when specifying data and function members.

The very least we need by way of a class interface is a constructor; a copy constructor because the space for the array will need to be allocated dynamically; an assignment operator because the compiler will supply an unsuitable version if there isn’t one defined; an overloaded subscript operator; and finally a destructor. With this in mind, the initial definition of the template looks like this:

template <typename T>

class Array

{

private:

T* elements;                              // Array of type T

size_t size;                              // Number of array elements

public:

explicit Array<T>(size_t arraySize);      // Constructor

Array<T>(const Array<T>& array);          // Copy Constructor

∼Array<T>();                              // Destructor

T& operator[](size_t index);              // Subscript operator

const T& operator[](size_t index) const;  // Subscript operator-const arrays

Array<T>& operator=(const Array<T>& rhs); // Assignment operator

};

The body of the template looks much like a regular class definition, except for the type parameter, T, in various places. For example, it has a data member, elements, which is of type pointer to T (equivalent to array of T). When the template is instantiated to produce a specific class definition, T is replaced by the actual type used to instantiate the template. If you create an instance of the template for type double, elements will be of type double* or array of double. The operations that the template needs to perform on objects of type T will obviously place requirements on the definition of type T when T is a class type.

The first constructor is declared as explicit to prevent its use for implicit conversions. The subscript operator has been overloaded on const. The non-const version of the subscript operator applies to non-const array objects and can return a non-const reference to an array element. Thus this version can appear on the left of an assignment. The const version is called for const objects and returns a const reference to an element; obviously this can’t appear on the left of an assignment.

The assignment operator function parameter is of type const Array<T>&. This type is const reference to Array<T>. When a class is synthesized from the template — with T as type double, for example — this is a const reference to the class name for that particular class, which would be const Array<double>, in this case. More generally, the class name for a specific instance of a template is formed from the template name followed by the actual type argument between angled brackets. The template name followed by the list of parameter names between angled brackets is called the template ID.

It’s not essential to use the full template ID within a template definition. Within the body of the Array template, Array by itself will be taken to mean Array<T>, and Array& will be interpreted as Array<T>&, so I can simplify the class template definition:

template <typename T>

class Array

{

private:

T* elements;                              // Array of type T

size_t size;                              // Number of array elements

public:

explicit Array(size_t arraySize);         // Constructor

Array(const Array& array);                // Copy Constructor

∼Array();                                 // Destructor

T& operator[](size_t index);              // Subscript operator

const T& operator[](size_t index) const;  // Subscript operator-const arrays

Array& operator=(const Array& rhs);       // Assignment operator

size_t getSize() { return size; }         // Accessor for size

};

Caution

You must use the template ID to identify the template outside the body of the template. This will apply function members of a class template that are defined outside the template.

It’s desirable that the number of elements in an Array<T> object can be determined so the getSize() member provides this. The assignment operator allows one Array<T> object to be assigned to another, which is something you can’t do with ordinary arrays. If you wanted to inhibit this capability, you would still need to declare the operator=() function as a member of the template. If you don’t, the compiler will create a public default assignment operator when necessary for a template instance. To prevent use of the assignment operator, just declare it as a private member of the class or use =delete in the declaration to prevent the compiler from supplying the default; then it can’t be accessed. Of course, you don’t need an implementation for the function member in this case, because you are not required to implement a function member unless it is used, and a private member will never be used outside the class. The getSize() member is implemented within the class template so it’s inline by default and no external definition is necessary.

Defining Function Members of a Class Template

If you include the definitions for the function members of a class template within its body, they are implicitly inline in any instance of the template, just like in an ordinary class. However, you’ll want to define members outside of the template body from time to time, especially if they involve a lot of code. The syntax for doing this is a little different from what applies for a normal class.

The clue to understanding the syntax is to realize that external definitions for function members of a class template are themselves templates. This is true even if a function member has no dependence on the type parameter T, so getSize() would need a template definition if it was not defined inside the class template. The parameter list for the template that defines a function member must be identical to that of the class template. Let’s start by defining the constructors for the Array template.

Constructor Templates

When you’re defining a constructor outside a class template definition, its name must be qualified by the class template name in a similar way to a function member of an ordinary class. However, this isn’t a function definition, it’s a template for a function definition, so that has to be expressed as well. Here’s the definition of the constructor:

template <typename T>                       // This is a template with parameter T

Array<T>::Array(size_t arraySize) : size {arraySize}, elements {new T[arraySize]}

{}

The first line identifies this as a template and also specifies the template parameter as T. Splitting the template function declaration into two lines, as I’ve done here, is only for illustrative purposes, and isn’t necessary if the whole construct fits on one line. The template parameter is essential in the qualification of the constructor name because it ties the function definition to the class template. Note that you don’t use the typename keyword in the qualifier for the member name; it’s only used in the template parameter list. You don’t need a parameter list after the constructor name. When the constructor is instantiated for an instance of the class template — for type double for example — the type name replaces T in the constructor qualifier, so the qualified constructor name for the class Array<double> is Array<double>::Array().

In the constructor, you must allocate memory in the free store for an elements array that contains size elements of type T. If T is a class type, a public default constructor must exist in the class T. If it doesn’t, the instance of this constructor won’t compile. The operator new throws a bad_alloc exception if the memory can’t be allocated for any reason, so you might want to put the body of the Array constructor in a try block:

template <typename T>                       // This is a template with parameter T

Array<T>::Array(size_t arraySize) try : size {arraySize}, elements {new T[arraySize]}

{}

catch (std::bad_alloc&)

{

std::cerr << "memory allocation failed for Array object.\n";

}

This will output a message to std::cerr if new fails to allocate the memory. cerr is the standard error output stream defined in the iostream header. It encapsulates the same destination as cout but ensures the stream is flushed so the output appears immediately. The parameter name is omitted from the catch block parameter list because it is not referenced; some compilers will issue a warning for local variables that are never referenced.

Of course, members defined within a class template body are inline by default and you can specify this too for an external template for a function member of a class template:

template <typename T>                       // This is a template with parameter T

inline Array<T>::Array(size_t arraySize)

try : size {arraySize}, elements {new T[arraySize]}

{}

catch (std::bad_alloc&)

{

std::cerr << "memory allocation failed for Array object.\n";

}

You place the inline keyword following the template parameter list and preceding the member name.

The copy constructor has to create an array for the object being created that’s the same size as that of its argument, and then copy the latter’s data members to the former. Here’s the code to do that:

template <typename T>

inline Array<T>::Array(const Array& array)

try : size {array.size}, elements {new T[array.size]}

{

for (size_t i {}; i < size; ++i)

elements[i] = array.elements[i];

}

catch (std::bad_alloc&)

{

std::cerr << "memory allocation failed for Array object copy.\n";

}

This assumes that the assignment operator works for type T. This demonstrates how important it is to always define the assignment operator for classes that allocate memory dynamically. If the class T doesn’t define it, the default for T is used, with undesirable side effects if creating a T object involves allocating memory dynamically. Without seeing the code for the template before you use it, you may not realize the dependency on the assignment operator. Because the copy constructor also allocates memory using the new operator, the body is also in a try block and the catch block issues a message that identifies where the memory allocation failed.

The Destructor Template

In many cases a default constructor will be OK in a class generated from a template but this is not the case here. The destructor must release the memory for the elements array, so its definition will be:

template <typename T> inline Array<T>::∼Array()

{

delete[] elements;

}

We are releasing memory allocated for an array so we must use the delete[] form of the operator. Failing to define this template would result in all classes generated from the template having major memory leaks.

Subscript Operator Templates

The operator[]() function is quite straightforward, but we must ensure illegal index values can’t be used. For an index value that is out of range, we can throw an exception:

template <typename T> inline T& Array<T>::operator[](size_t index)

{

if (index >=size) throw std::out_of_range {"Index too large: " + std::to_string(index)};

return elements[index];

}

I could define an exception class to use here, but it’s easier to borrow the out_of_range class type that’s already defined in the stdexcept header. This is thrown if you index a string object with an out of range index value for example, so the usage here is consistent with that. An exception of type out_of_range is thrown if the value of index is not between 0 and size-1. The argument to the out_of_range constructor is a string object that describes the error and includes the erroneous index value. A null-terminated string (type const char*) corresponding to the string object is returned by the what() member of the exception object. The argument that is passed to the out_of_range constructor is a message that includes the erroneous index value to make tracking down the source of the problem a little easier. An index cannot be less than zero because it is of type size_t, which is an unsigned integer type.

The const version of the subscript operator function will be almost identical to the non-const version:

template <typename T> inline const T& Array<T>::operator[](size_t index) const

{

if (index >=size) throw std::out_of_range {"Index too large: " + std::to_string(index)};

return elements[index];

}

The Assignment Operator Template

There’s more than one possibility for how the assignment operator works. The operands must be of the same Array<T> type with the same T but this does not prevent the size members from having different values. You could implement the assignment operator so that the left operand retains the same value for its size member. If the left operand has fewer elements than the right operand, you would just copy sufficient elements from the right operand to fill the array for the left operand. If the left operand has more elements than the right operand, you could either leave the excess elements at their original values or set them to the value produced by the default T constructor.

To keep it simple, I’ll just make the left operand have the same size value as the right operand. To implement this, the assignment operator function must release any memory allocated in the destination object and then do what the copy constructor did, after checking that the objects are not identical of course. Here’s the definition:

template <typename T> inline Array<T>& Array<T>::operator=(const Array& rhs)

try

{

if (&rhs != this)                         // If lhs != rhs...

{                                         // ...do the assignment...

if (elements)                           // If lhs array exists

delete[] elements;                    // release the free store memory

size = rhs.size;

elements = new T[rhs.size];

for (size_t i {}; i < size; ++i)

elements[i] = rhs.elements[i];

}

return *this;                             // ... return lhs

}

catch (std::bad_alloc&)

{

std::cerr << "memory allocation failed for Array assignment.\n";

}

Checking to make sure that the left operand is identical to the right is essential; otherwise you’d free the memory for the elements member of the object pointed to by this, then attempt to copy it to itself when it no longer exists. When the operands are different, you release any heap memory owned by the left operand before creating a copy of the right operand. This has the potential to throw bad_alloc so I have put the function body in a try block too. Heap memory allocation failure is a rare occurrence these days because physical memory is large and virtual memory is very large so checking for bad_alloc is often omitted. If bad_alloc is thrown, you’ll definitely know about it anyway. I’ll omit the try/catch combination for bad_alloc from any subsequent examples where it might apply to keep the code shorter and less cluttered.

All the function member definitions that you’ve written here are templates and they are inextricably bound to the class template. They are not function definitions, they’re templates to be used by the compiler when the code for one of the member functions of the class template needs to be generated, so they need to be available in any source file that uses the template. For this reason, you’d normally put all the definitions of the member functions for a class template in the header file that contains the class template itself.

Instantiating a Class Template

The compiler instantiates a class template as a result of a definition of an object that has a type produced by the template. Here’s an example:

Array<int> data {40};

When this statement is compiled, two things happen: the definition for the Array<int> class is created so that the type is identified, and the constructor definition is generated because it must be called to create the object. This is all that the compiler needs to create the data object so this is the only code that it provides from the templates at this point.

The class definition that’ll be included in the program is generated by substituting int in place of T in the template, but there’s one subtlety. The compiler only compiles the member functions that your program uses, so you do not necessarily get the entire class that would be produced by a simple substitution for the template parameter. On the basis of just the definition for the object, data, it is equivalent to:

class Array<int>

{

private:

int* elements;                            // Array of type int

size_t size;                              // Number of array elements

public:

Array(size_t arraySize);                  // Constructor

};

You can see that the only function member is the constructor. The compiler won’t create instances of anything that isn’t required to create the object, and it won’t include parts of the template that aren’t needed in the program. This implies that there can be coding errors in a class template and a program that uses the template may still compile, link, and run successfully. If the errors are in parts of the template that aren’t required by the program, they won’t be detected by the compiler because they are not included in the code that is compiled. Obviously, you are almost certain to have other statements in a program besides the declaration of an object that use other function members — for instance, you’ll always need the destructor to destroy the object — so the ultimate version of the class in the program will include more than that shown in the preceding code. The point is that what is finally in the class generated from the template will be precisely those parts that are actually used in the program, which is not necessarily the complete template.

Caution

Of course, this implies that you must take care when testing your own class templates to ensure that all the function members are generated and tested. You also need to consider what the template does across a range of types so you need to test a template with pointers and references as the template type argument.

The instantiation of a class template from a definition is referred to as an implicit instantiation of the template, because it arises as a by-product of declaring an object. This terminology is also to distinguish it from an explicit instantiation of a template, which I’ll get to shortly and which behaves a little differently.

As I said, the declaration of data also causes the constructor, Array<int>::Array(), to be called, so the compiler uses the function template that defines the constructor to create a definition for the constructor for the class:

inline Array<int>::Array(size_t arraySize) try : size {arraySize}, elements {new int[arraySize]}

{}

catch (std::bad_alloc&)

{

std::cerr << "memory allocation failed for Array object creation.\n";

}

Each time you define a variable using a class template with a different type argument, a new class is defined and included in the program. Because creating the class object requires a constructor to be called, the definition of the appropriate class constructor is also generated. Of course, creating objects of a type that you’ve created previously doesn’t necessitate any new template instances. The compiler uses any previously created template instances as required.

When you use the function members of a particular instance of a class template — by calling functions on the object that you defined using the template, for example — the code for each member function that you use is generated. If you have member functions that you don’t use, no instances of their templates are created. The creation of each function definition is an implicit template instantiation because it arises out of the use of the function. The template itself isn’t part of your executable code. All it does is enable the compiler to generate the code that you need automatically. This process is illustrated in Figure 16-3.

Figure 16-3.
figure 3figure 3

Implicit instantiation of a class template

Note that a class template is only implicitly instantiated when an object of the specific template type needs to be created. Declaring a pointer to an object type won’t cause an instance of the template to be created. Here’s an example:

Array<string>* pObject;                     // A pointer to a template type

This defines pObject as type pointer to type Array<string>. No object of type Array<string> is created as a result of this statement so no template instance is created. Contrast this with the following statement:

Array<std::string*> pMessages {10};

This time the compiler does create an instance of the class template. This defines an Array<std::string*> object so each element of pMessages can store a pointer to an std::string object. An instance of the template defining the constructor is also generated. Let’s try out the Array template in a working example. You can put the class template and the templates defining the member functions of the template all together in a header file Array.h:

// Array class template definition

#ifndef ARRAY_H

#define ARRAY_H

#include <stdexcept>                        // For standard exception types

#include <string>                           // For to_string()

// Definition of the Array<T> template...

// Definitions of the templates for function members of Array<T>...

#endif

To use the class template, you just need a program that’ll declare some arrays using the template and try them out. The example will create an Array of Box objects - you can use this definition for the Box class:

// Box.h

#ifndef BOX_H

#define BOX_H

class Box

{

protected:

double length {1.0};

double width {1.0};

double height {1.0};

public:

Box(double lv, double wv, double hv) : length {lv}, width {wv}, height {hv} {}

Box() = default;

double volume() const { return length*width*height; }

};

#endif

I’ll use some out-of-range index values in the example, just to show that it works:

// Ex16_01.cpp

// Using a class template

#include "Box.h"

#include "Array.h"

#include <iostream>

#include <iomanip>

int main()

{

const size_t nValues {50};

Array<double> values {nValues};                // Class constructor instance created

try

{

for (size_t i {}; i < nValues; ++i)

values[i] = i + 1;                         // Member function instance created

std::cout << "Sums of pairs of elements:";

size_t lines {};

for (size_t i {nValues - 1} ; i >=0 ; i--)

std::cout << (lines++ % 5 == 0 ? "\n" : "")

<< std::setw(5) << values[i] + values[i - 1];

}

catch (const std::out_of_range& ex)

{

std::cerr << "\nout_of_range exception object caught! " << ex.what() << std::endl;

}

try

{

const size_t nBoxes {10};

Array<Box> boxes {nBoxes};                   // Template instance created

for (size_t i {} ; i <= nBoxes ; ++i)        // Member instance created in loop

std::cout << "Box volume is " << boxes[i].volume() << std::endl;

}

catch (const std::out_of_range& ex)

{

std::cerr << "\nout_of_range exception object caught! " << ex.what() << std::endl;

}

}

This example will produce the following output:

Sums of pairs of elements:

99   97   95   93   91

89   87   85   83   81

79   77   75   73   71

69   67   65   63   61

59   57   55   53   51

49   47   45   43   41

39   37   35   33   31

29   27   25   23   21

19   17   15   13   11

9    7    5    3

out_of_range exception object caught! Index too large: 4294967295

Box volume is 1

Box volume is 1

Box volume is 1

Box volume is 1

Box volume is 1

Box volume is 1

Box volume is 1

Box volume is 1

Box volume is 1

Box volume is 1

out_of_range exception object caught! Index too large: 10

The main() function creates an object of type Array<double> that implicitly creates an instance of the class template with a type argument of double. The number of elements in the array is specified by the argument to the constructor, nValues. The compiler will also create an instance of the template for the constructor definition.

Within the first try block, the elements of the values object with are initialized values from 1 to nValues in a for loop. The expression values[i] results in an instance of the subscript operator function being created. This instance is called implicitly by this expression as values.operator[](i). Because values is not const, the non-const version of the operator function is called. A second for loop in the try block outputs the sums of successive pairs of elements, starting at the end of the array. The code in this loop also calls the subscript operator function, but because the instance of the function template has already been created, no new instance is generated. Clearly, the expression values[i-1] has an illegal index value when i is 0, so this causes an exception to be thrown by the operator[]() function. The catch block catches this and outputs a message to the standard error stream. The what() function for the out_of_range exception returns a null-terminated string that corresponds to the string object passed to the constructor when the exception object was created. You can see from the output that the exception was thrown by the overloaded subscript operator function and that the index value is very large. The value of the index suggests that it originated by decrementing an unsigned zero value.

When the exception is thrown by the subscript operator function, control is passed immediately to the handler, so the illegal element reference is not used and nothing is stored at the location indicated by the illegal index. Of course, the loop also ends immediately at this point.

The next try block defines an object that can store Box objects. This time, the compiler generates an instance of the class template, Array<Box>, which stores an array of Box objects, because the template has not been instantiated for Box objects previously. The statement also calls the constructor to create the boxes object so an instance of the function template for the constructor is created. The constructor for the Array<Box> class calls the default constructor for the Box class when the elements member is created in the free store. Of course, all the Box objects in the elements array have the default dimensions of 1 × 1 × 1.

The volume of each Box object in boxes is output in a for loop. The expression boxes[i] calls the overloaded subscript operator, so again the compiler uses an instance of the template to produce a definition of this function. When i has the value nBoxes, the subscript operator function throws an exception because an index value of nBoxes is beyond the end of the elements array. The catch block following the try block catches the exception. Because the try block is exited, all locally declared objects will be destroyed, including the boxes object. The values object still exists at this point because it was created before the first try block and it is still in scope.

Static Members of a Class Template

A class template can have static members, just like an ordinary class. Static function members of a template class are quite straightforward. Each instance of a class template instantiates the static function member as needed. A static function member has no this pointer and therefore can’t refer to non-static members of the class. The rules for defining static function members of a class template are the same as those for a class, and a static function member of a class template behaves in each instance of the template just as if it were in an ordinary class.

A static data member is a little more interesting because it needs to be initialized outside the template definition. Suppose the Array<T> template contained a static data member of type T:

template <typename T>

class Array

{

private:

static T value;                           // Static data member

T* elements;                              // Array of type T

size_t size;                              // Number of array elements

public:

explicit Array(size_t arraySize);         // Constructor

Array(const Array& array);                // Copy Constructor

∼Array();                                 // Destructor

T& operator[](size_t index);              // Subscript operator

const T& operator[](size_t index) const;  // Subscript operator-const arrays

Array& operator=(const Array& rhs);       // Assignment operator

size_t getSize() { return size; }         // Accessor for size

};

The initialization for the value member is accomplished through a template in the same header file:

template <typename T> T Array<T>::value;    // Initialize static data member

This initializes value with the equivalent of 0 so it effectively calls the default constructor for type T. A static data member is always dependent on the parameters of the template of which it is a member regardless of its type, so you must initialize value as a template with parameter T. The static variable name must also be qualified with the type name Array<T> so that it’s identified with the instance of the class template that is generated. You can’t use Array by itself here, because this template for initializing value is outside the body of the class template, and the template ID is Array<T>. Suppose you wanted a static member of Array<T> to define a minimum number of elements:

template <typename T>

class Array

{

private:

static size_t minSize;                    // Minimum number of elements

// Rest of the class as before...

};

Even though minSize is of a fundamental type - size_t is an alias for an unsigned integer type - you still must initialize it in a template and qualify the member name with the template ID:

template<typename T> size_t Array<T>::minSize {5};

minSize can only exist as a member of a class generated from the Array<T> template and each such class has its own minSize member. It is therefore inevitable that it can only be initialized through another template. Note that creating an instance of a template does not guarantee that a static data member is defined. A static data member of a class template will only be defined if it is used because the compiler will only process the template that initializes the static data member when the member is used.

Non-Type Class Template Parameters

A non-type parameter looks like a function parameter — a type name followed by the name of the parameter. Therefore, the argument for a non-type parameter is a value of the given type. However, you can’t use just any type for a non-type parameter in a class template. Non-type parameters are intended to be used to define values that might be useful in specifying a container, such as array dimensions or other size specification, or possibly as upper and lower limits for index values.

A non-type parameter can only be an integral type, such as size_t or long; an enumeration type; a pointer or a reference to an object, such as string* or Box&; a pointer or a reference to a function; or a pointer to a member of a class. You can conclude from this that a non-type parameter can’t be a floating point type or any class type, so types double, Box, and std::string are not allowed, and neither is std::string**. Remember that the primary rationale for non-type parameters is to allow sizes and range limits for containers to be specified. Of course, the argument corresponding to a non-type parameter can be an object of a class type, as long as the parameter type is a reference. For a parameter of type Box&, for example, you could use any object of type Box as an argument.

A non-type parameter is written just like a function parameter, with a type name followed by a parameter name. Here’s an example:

template <typename T, size_t size>

class ClassName

{

// Definition using T and size...

};

This template has a type parameter, T, and a non-type parameter, size. The definition is expressed in terms of these two parameters and the template name. If you need it, the type name of a type parameter can also be the type for a non-type parameter:

template <typename T,                       // T is the name of the type parameter

size_t size,

T value>                          // T is also the type of this non-type parameter

class ClassName

{

// Definition using T, size, and value...

};

This template has a non-type parameter, value, of type T. The parameter T must appear before its use in the parameter list, so value couldn’t precede the type parameter T here. Note that using the same symbol with the type and non-type parameters implicitly restricts the possible arguments for the type parameter to the types permitted for a non-type argument (in other words, T can only be an integral type).

To illustrate how you could use non-type parameters, suppose you defined the class template for arrays as follows:

template <typename T, size_t arraySize, T value>

class Array

{

// Definition using T, size, and value...

};

You could now use the non-type parameter, value, to initialize each element of the array in the constructor:

template <typename T, int arraySize, T value>

Array<T, arraySize, value>::Array(size_t arraySize) : size {arraySize}, elements {new T[size]}

{

for(size_t i {} ; i < size ; ++i)

elements[i] = value;

}

This is not a very intelligent approach to initializing the members of the array. This places a serious constraint on the types that are legal for T. Because T is used as the type for a non-type parameter it is subject to the constraints on non-type parameter types. A non-type parameter can only be an integral type, a pointer, or a reference, so you can’t create Array objects to store double values or Box objects, so the usefulness of this template is somewhat restricted.

To provide a more credible example, I’ll add a non-type parameter to the Array template to allow flexibility in indexing the array:

template <typename T, int startIndex>

class Array

{

private:

T* elements;                              // Array of type T

size_t size;                              // Number of array elements

public:

explicit Array(size_t arraySize);         // Constructor

Array(const Array& array);                // Copy Constructor

∼Array();                                 // Destructor

T& operator[](int index);                 // Subscript operator

const T& operator[](int index) const;     // Subscript operator-const arrays

Array& operator=(const Array& rhs);       // Assignment operator

size_t getSize() { return size; }         // Accessor for size

};

This adds a non-type parameter, startIndex of type int. The idea is that you can specify that you want to use index values that vary over a given range. For example, to create an Array<> object that allows index values from -10 to +10, you would specify the array with the non-type parameter value as –10 and the argument to the constructor as 21 because the array would need 21 elements. Index values can now be negative so the parameter for the subscript operator functions has been changed to type int.

Because the class template now has two parameters, the templates defining the member functions of the class template must have the same two parameters. This is necessary even if some of the functions aren’t going to use the non-type parameters. The parameters are part of the identification for the class template, so to match the template, they must have the same parameter list.

There are some serious disadvantages to what I have done here. A consequence of adding the startIndex template parameter is that different values for the argument generate different template instances. This means that an array of double values indexed from 0 will be a different type from an array of double values indexed from 1. If you use both in a program, two independent class definitions will be created from the template, each with whatever member functions you use. This has at least two undesirable consequences: first, you’ll get a lot more compiled code in your program than you might have anticipated (a condition often known as code bloat); second (and far worse), you won’t be able to intermix elements of the two types in an expression. It would be much better to provide flexibility for the range of index values by adding a parameter to the constructor rather than using a non-type template parameter. Here’s how that would look:

template <typename T>

class Array

{

private:

T* elements;                                        // Array of type T

size_t size;                                        // Number of array elements

int start;                                          // Starting index value

public:

explicit Array(size_t arraySize, int startIndex=0); // Constructor

T& operator[](int index);                           // Subscript operator

const T& operator[](int index) const;               // Subscript operator-const arrays

Array& operator=(const Array& rhs);                 // Assignment operator

size_t getSize() { return size; }                   // Accessor for size

};

The extra member, start, stores the starting index for the array specified by the second constructor argument. The default value for the startIndex parameter is 0, so normal indexing is obtained by default.

In the interests of seeing how the function members are defined when you have a non-template parameter, let’s ignore the better definition and complete the set of function templates that you need for the version of the Array class template with the additional non-type parameter.

Templates for Function Members with Non-Type Parameters

Because you’ve added a non-type parameter to the class template definition, the code for the templates for all function members needs to be changed. The template for the constructor is:

template <typename T, int startIndex>

inline Array<T, startIndex>::Array(size_t arraySize) :

size {arraySize}, elements {new T[arraySize]}

{}

The template ID is now Array<T, startIndex>, so this is used to qualify the constructor name. This is the only change from the original definition apart from adding the new template parameter to the template and omitting the try/catch blocks to deal with bad_alloc.

For the copy constructor, the changes to the template are similar:

template <typename T, int startIndex>

inline Array<T, startIndex>::Array(const Array& array) :

size {array.size}, elements {new T[array.size]}

{

for (size_t i {} ; i < size ; ++i)

elements[i] = array.elements[i];

}

Of course, the external indexing of the array doesn’t affect how you access the array internally; it’s still indexed from zero here.

The destructor only needs the extra template parameter:

template <typename T, int startIndex>

inline Array<T, startIndex>::∼Array()

{

delete[] elements;

}

The template definition for the non-const subscript operator function now becomes:

template <typename T, int startIndex>

T& Array<T, startIndex>::operator[](int index)

{

if (index > startIndex + static_cast<int>(size) - 1)

throw std::out_of_range {"Index too large: " + std::to_string(index)};

if(index < startIndex)

throw std::out_of_range {"Index too small: " + std::to_string(index)};

return elements[index - startIndex];

}

Significant changes have been made here. The index parameter is of type int to allow negative values. The validity checks on the index value now verify that it’s between the limits determined by the non-type template parameter and the number of elements in the array. Index values can only be from startIndex to startIndex+size-1. Because size_t is usually an unsigned integer type you must explicitly cast it to int; if you don’t, the other values in the expression will be implicitly converted to size_t, which will produce a wrong result if startIndex is negative. The choice of message for the exception and the expression selecting it has also been changed.

The const version of the subscript operator function changes in a similar fashion:

template <typename T, int startIndex>

const T& Array<T, startIndex>::operator[](int index) const

{

if (index > startIndex + static_cast<int>(size) - 1)

throw std::out_of_range {"Index too large: " + std::to_string(index)};

if(index < startIndex)

throw std::out_of_range {"Index too small: " + std::to_string(index)};

return elements[index - startIndex];

}

Finally, you need to alter the template for the assignment operator, but only the template parameter list and the template ID that qualifies the operator name need to be modified:

template <typename T, int startIndex>

Array<T, startIndex>& Array<T, startIndex>::operator=(const Array& rhs)

{

if (&rhs != this)                         // If lhs != rhs...

{                                         // ...do the assignment...

if (elements)                           // If lhs array exists

delete[] elements;                    // release the free store memory

size = rhs.size;

elements = new T[rhs.size];

for (size_t i {}; i < size; ++i)

elements[i] = rhs.elements[i];

}

return *this;                             // ... return lhs

}

There are restrictions on how you use a non-type parameter within a template. In particular, you must not modify the value of a parameter within the template definition. Consequently, a non-type parameter cannot be used on the left of an assignment or have the increment or decrement operator applied to it — in other words, it’s treated as a constant. All parameters in a class template must always be specified to create an instance, unless there are default values for them. I’ll discuss the use of default argument values for class template parameters later in the chapter.

In spite of the shortcomings of the Array template with a non-type parameter, let’s see it in action in a working example. You just need to assemble the definitions for the function member templates into a header file together with the Array template definition with the non-type parameter. The following example will exercise the new features using Box.h from Ex16_01:

// Ex16_02.cpp

// Using a class template with a non-type parameter

#include "Box.h"

#include "Array.h"

#include <iostream>

#include <iomanip>

int main()

try

{

try

{

const size_t size {21};                              // Number of array elements

const int start {-10};                               // Index for first element

const int end {start + static_cast<int>(size) - 1};  // Index for last element

Array<double, start> values {size};                  // Define array of double values

for (int i {start}; i <= end; ++i)                   // Initialize the elements

values[i] = i - start + 1;

std::cout << "Sums of pairs of elements: ";

size_t lines {};

for (int i {end} ; i >=start; --i)

std::cout << (lines++ % 5 == 0 ? "\n" : "")

<< std::setw(5) << values[i] + values[i - 1];

}

catch (const std::out_of_range& ex)

{

std::cerr << "\nout_of_range exception object caught! " << ex.what() << std::endl;

}

const int start {};

const size_t size {11};

Array<Box, start - 5> boxes {size};                    // Create array of Box objects

for (int i {start - 5}; i <= start + static_cast<int>(size) - 5; ++i)

std::cout << "Box[" << i << "] volume is " << boxes[i].volume() << std::endl;

}

catch (const std::exception& ex)

{

std::cerr << typeid(ex).name() << " exception caught in main()! "

<< ex.what() << std::endl;

}

This displays the following output:

Sums of pairs of elements:

41   39   37   35   33

31   29   27   25   23

21   19   17   15   13

11    9    7    5    3

out_of_range exception object caught! Index too small: -11

Box[-5] volume is 1

Box[-4] volume is 1

Box[-3] volume is 1

Box[-2] volume is 1

Box[-1] volume is 1

Box[0] volume is 1

Box[1] volume is 1

Box[2] volume is 1

Box[3] volume is 1

Box[4] volume is 1

Box[5] volume is 1

class std::out_of_range exception caught in main()! Index too large: 6

The body of main() is a try block that catches any uncaught exceptions that have std::exception as a base class so std::bad_alloc will be caught by this. The nested try block, starts by defining constants that specify the range of index values and the size of the array. The size and start variables are used to create an instance of the Array template to store 21 values of type double. The second template argument corresponds to the non-type parameter and specifies the lower limit for the index values of the array. The size of the array is specified by the constructor argument.

The for loop that follows assigns values to the elements of the values object. The loop index, i, runs from the lower limit start, which will be –10, up to and including the upper limit end, which will be +10. Within the loop the values of the array elements are set to run from 1 to 21.

Next the sums of pairs of successive elements are output starting at the last array element and counting down. The lines variable is used to output the sums five to a line. As in the earlier example, sloppy control of the index value results in the expression values[i–1] causing an out_of_range exception to be thrown. The handler for the nested try block catches it and displays the message you see in the output.

The statement that creates an array to store Box objects is in the outer try block that is the body of main(). The type for boxes is Array<Box,start-5>, which demonstrates that expressions are acceptable as argument values for non-type parameters in a template instantiation. Such an expression must either evaluate to a value that has the type of the parameter, or it must be possible to convert the result to the appropriate type by means of an implicit conversion. You need to take care if such an expression includes the > character. Here’s an example:

Array<Box, start > 5 ? start : 5> boxes;    // Will not compile!

The intent of the expression for the second argument that uses the conditional operator is to supply a value of at least 5, but as it stands, this won’t compile. The > in the expression is paired with the opening angled bracket, and closes the parameter list. Parentheses are necessary to make the statement valid:

Array<Box, (start > 5 ? start : 5)> boxes;  // OK

Parentheses are also likely to be necessary for expressions for non-type parameters that involve the arrow operator (->), or the shift right operator (>>).

The next for loop throws another exception, this time because the index exceeds the upper limit. The exception is caught by the catch block for the body of main(). The parameter is a reference to the base class and the output shows that the exception is identified as type std::out_of_range, thus demonstrating there is no object slicing occurring with a reference parameter. There’s a significant difference between the ways the two exceptions were caught. Catching the exception in a catch block for the body of main() means that the program ends at this point. The previous exception was caught inside the body of main() in the catch block for the nested try block so it was possible to allow program execution to continue.

You must always keep in mind that non-type parameter arguments in a class template are part of the type of an instance of the template. Every unique combination of template arguments produces another class type. As I indicated earlier, the usefulness of the Array<T,int> template is very restricted compared to the original. You can’t assign an array of ten values of a given type to another array of ten values of the same type if the starting indexes for the arrays are different — the arrays will be of different types. The class template with an extra data member and an extra constructor parameter is much more effective. You should always think twice about using non-type parameters in a class template to be sure that they’re really necessary. Often you’ll be able to use an alternative approach that will provide a more flexible template and more efficient code.

Arguments for Non-Type Parameters

An argument for a non-type parameter that is not a reference or a pointer must be a compile-time constant expression. This means that you can’t use an expression containing a non-const integer variable as an argument, which is a slight disadvantage, but the compiler will validate the argument, which is a compensating plus. For example, the following statements won’t compile:

int start {-10};

Array<double, start> values(21);            // Won't compile because start is not const

The compiler will generate a message to the effect that the second argument here is invalid. Here are correct versions of these two statements:

const int start {-10};

Array<double, start> values(21);           // OK

Now that start has been declared as const, the compiler can rely on its value, and both template arguments are now legal. The compiler applies standard conversions to arguments when they are necessary to match the parameter type. For example, if you had a non-type parameter declared as type const size_t, the compiler converts an integer literal such as 10 to the required argument type.

Pointers and Arrays as Non-Type Parameters

The argument for a non-type parameter that is a pointer must be an address, but it can’t be any old address. It must be the address of an object or function with external linkage; so for example, you can’t use addresses of array elements or addresses of non-static class members as arguments. This also means that if a non-type parameter is of type const char*, you can’t use a string literal as an argument when you instantiate the template. If you want to use a string literal as an argument in this case, you must initialize a pointer variable with the address of the string literal, and pass the pointer as the template argument.

Because a pointer is a legal non-type template parameter, you can specify an array as a parameter, but an array and a pointer are not always interchangeable when supplying arguments to a template. For example, you could define a template as follows:

template <long* numbers>

class MyClass

{

// Template definition...

};

You can now create instances of this template with the following code:

long data[10];                              // Global

long* pData {data};                         // Global

MyClass<pData> values;

MyClass<data> values;

Either an array name or a pointer of the appropriate type can be used as an argument corresponding to a parameter that is a pointer. However, the converse is not the case. Suppose that you have defined this template:

template <long numbers[10]>

class AnotherClass

{

// Template definition...

};

The parameter is an array with 10 elements of type long, and the argument must be of the same type. In this case, you can the data array defined earlier as the template argument:

AnotherClass<data> numbers;                 // OK

However, you can’t use a pointer, so the following won’t compile:

AnotherClass<pData> numbers;                // Not allowed!

The reason is that an array type is quite different from a pointer type; an array name by itself represents as address but it is not a pointer and cannot be modified in the way that a pointer can.

Default Values for Template Parameters

You can supply default argument values for both type and non-type parameters in a class template. This works in a similar way to default values for function parameters - if a given parameter has a default value, then all subsequent parameters in the list must also have default values specified. If you omit an argument for a template parameter that has a default value specified, the default is used, just like with default parameter values in a function. Similarly, when you omit the argument for a given parameter in the list, all subsequent arguments must also be omitted.

The default values for class template parameters are written in the same way as defaults for function parameters — following an = after the parameter name. You could supply defaults for both the parameters in the version of the Array template with a non-type parameter. Here’s an example:

template < typename T = int, int startIndex = 0>

class Array

{

// Template definition as before...

};

You don’t need to specify the default values in the templates for the member functions; the compiler will use the argument values used to instantiate the class template.

You could omit all the template arguments to declare an array of elements of type int indexed from 0.

Array<> numbers {101};

The legal index values run from 0 to 100, as determined by the default value for the non-type template parameter and the argument to the constructor. You must still supply the angled brackets, even though no arguments are necessary. The other possibilities open to you are to omit the second argument or to supply them all, as shown here:

Array<string, -100> messages {200};         // Array of 200 string objects indexed from -100

Array<Box> boxes {101};                     // Array of 101 Box objects indexed from 0

If a class template has default values for any of its parameters, they only need to be specified in the first declaration of the template in a source file, which usually will be the definition of the class template.

Explicit Template Instantiation

So far in this chapter, instances of a class template have been created implicitly as a result of defining a variable of a template type. You can also explicitly instantiate a class template without defining an object of the template type. The effect of an explicit instantiation of a template is that the compiler creates the instance determined by the parameter values that you specify.

You have already seen how to explicitly instantiate function templates back in Chapter 8. To instantiate a class template, just use the template keyword followed by the template class name and the template arguments between angled brackets. This statement explicitly creates an instance of the Array template:

template class Array<double, 1>;

This creates an instance of the template that stores values of type double, indexed from 1. Explicitly instantiating a class template generates the class type definition and it instantiates all of the function members of the class from their templates. This happens regardless of whether you call the function members so the executable may contain code that is never used.

Special Cases

You’ll encounter many situations where a class template definition won’t be satisfactory for every conceivable argument type. For example, you can compare string objects by using overloaded comparison operators, but you can’t do this with null-terminated strings. If a class template compares objects using the comparison operators, it will work for type string but not for type char*. To compare objects of type char*, you need to use the comparison functions that are declared in the cstring header.

To deal with this sort of problem, you have two options. The first possibility is to avoid the problem. You can use static_assert() that you met way back in Chapter 10 to test one or more of the type arguments. The second possibility is to define a class template specialization, which provides a class definition that is specific to a given set of arguments for the template parameters. I’ll explain how you use static_assert() first.

Using static_assert( ) in a Class Template

You can use static_assert() to cause the compiler to output a message and fail compilation when a type argument in a class template is not appropriate. static_assert() has two arguments; when the first argument is false, the compiler outputs the message specified by the second argument. To protect against misuse of a class template, the first argument to static_assert() will use one or more of the templates from the type_traits header. These test the properties of types and classify types in various ways. The templates in type_traits have the std::integral_constant template as a base, which defines a static member, value, which is a constant expression that is implicitly convertible to type bool. There are a lot of templates in the type_traits header so I’ll just mention a few in Table 16-1 to give you an idea of the possibilities, and leave you to explore the rest in your Standard Library documentation. These templates are all defined in the std namespace.

Template

Result

is_default_constructible<T>

The value member is only true if type T is default constructible, which means for a class type that the class has a no-arg constructor.

is_copy_constructible<T>

The value member is true if type T is copy constructible, which means for a class type that the class has a copy constructor.

is_assignable<T>

The value member is true if type T is assignable, which means for a class type that it has an assignment operator function.

is_pointer<T>

The value member is true if type T is a pointer type and false otherwise.

is_null_pointer<T>

The value member is true only if type T is of type std::nullptr_t.

is_class<T>

The value member is true only if type T is a class type.

It’s easy to get confused about what is happening with these templates. Keep in mind that these templates are relevant at compile time. A template such as is_assignable<T> will be compiled each time an instance of a template that uses it in a static_assert() is instantiated. The result therefore relates to the argument that was used to instantiate the template so the test applies to the template type argument. An example should make it clear how you use these.

Let’s amend Ex16_01 to show this in operation. First, comment out the line in the Box class definition in Box.h that generates the default constructor:

class Box

{

protected:

double length {1.0};

double width {1.0};

double height {1.0};

public:

Box(double lv, double wv, double hv) : length {lv}, width {wv}, height {hv} {}

// Box() = default;

double volume() const { return length*width*height; }

};

Next, add an #include directive for the type_traits header to Array.h and add one statement following the opening brace in the body of the Array template:

#include <stdexcept>                        // For standard exception types

#include <string>                           // For to_string()

#include <type_traits>

template <typename T>

class Array

{

static_assert(std::is_default_constructible<T>::value, "A default constructor is required.");

// Rest of the template as before...

};

You can now recompile the example, which will fail of course. The first argument to static_assert() is the value member of the instance of is_default_constructible<T> for the current type argument for T. When this is type Box, the value member of the template will be false, triggering the message you’ll see in the output from your compiler and the compilation will fail. Removing the commenting out of the Box default constructor will allow the compilation to succeed. The complete example is in the code download as Ex16_03.

Defining a Class Template Specialization

A class template specialization is a class definition, not a class template. Instead of using the template to generate the class from the template for a particular type, char* say, the compiler uses the specialization you define for that type instead. Thus a class template specialization provides a way to predefine instances of a class template to be used by the compiler for specific sets of argument for the template parameters.

Suppose it was necessary to create a specialization of the first version of the Array template for type char*. You’d write the specialization of the class template definition as follows:

template <>

class Array<char*>

{

// Definition of a class to suit type char*...

};

This definition of the specialization of the Array template for type char* must be preceded by the original template definition, or by a declaration for the original template. Because all the parameters are specified, it is called a complete specialization of the template, which is why the set of angle brackets following the template keyword are empty. The compiler will always use a class definition when it is available, so there’s no need for the compiler to consider instantiating the template for type char*.

It may be that just one or two function members of a class template need to have code specific to a particular type. If the function members are defined by separate templates outside the class template, rather than within the body of the class template, you can just provide specializations for the function templates that need to be different.

Partial Template Specialization

If you were specializing the version of the template with two parameters, you may only want to specify the type parameter for the specialization, leaving the non-type parameter open. You could do this with a partial specialization of the Array template that you could define like this:

template <int start>                       // Because there is a parameter...

class Array<char*, start>                  // This is a partial specialization...

{

// Definition to suit type char*...

};

This specialization of the original template is also a template. The parameter list following the template keyword must contain the parameters that need to be specified for an instance of this template specialization — just one in this case. The first parameter is omitted because it is now fixed. The angled brackets following the template name specify how the parameters in the original template definition are specialized. The list here must have the same number of parameters as appear in the original, unspecialized template. The first parameter for this specialization is char*. The other parameter is specified as the corresponding parameter name in this template.

Apart from the special considerations you might need to give to a template instance produced by using char* for a type parameter, it may well be that pointers in general are a specialized subset that need to be treated differently from objects and references. For example, to compare objects when a template is instantiated using a pointer type, pointers must be dereferenced, otherwise you are just comparing addresses, not the objects or values stored at those addresses.

For this situation, you can define another partial specialization of the template. The parameter is not completely fixed in this case, but it must fit within a particular pattern that you specify in the list following the template name. For example, a partial specialization of the Array template for pointers would look like this:

template <typename T, long start>

class Array<T*, start>

{

// Definition to suit pointer types other than char*...

};

The first parameter is still T, but the T* between angled brackets following the template name indicates that this definition is to be used for instances where T is specified as a pointer type. The other two parameters are still completely variable, so this specialization will apply to any instance where the first template argument is a pointer.

Choosing between Multiple Partial Specializations

Suppose both the partial specializations of the Array template that I just discussed were defined — the one for type char*, and the one for any pointer type. How can you be sure that the version for type char* is selected by the compiler when this is appropriate for any particular instantiation? For example, consider this declaration:

Array<Box*, -5> boxes {11};

Clearly, this only fits with the specialization for pointers in general, but both partial specializations fit the declaration if you write this:

Array<char*, 1> messages {100};

In this case, the compiler determines that the char* partial specialization is a better fit because it is more specialized than the alternative. The partially specialized template for char* is determined to be more specialized than the specialization for pointers in general because although anything that selects the char* specialization — which happens to be just char*— also selects the T* specialization, the reverse is not the case.

One specialization is more specialized than another when every argument that matches the given specialization matches the other, but the reverse is not true. Thus you can consider a set of specializations for a template to be ordered from most specialized to least specialized. When several template specializations may fit a given declaration, the compiler will select and apply the most specialized specialization from these.

Friends of Class Templates

Because a class can have friends, you won’t be surprised to learn that a class template can also have friends. Friends of a class template can be classes, functions, or other templates. If a class is a friend of a class template, then all its function members are friends of every instance of the template. A function that is a friend of a template is a friend of any instance of the template, as shown in Figure 16-4.

Figure 16-4.
figure 4figure 4

A friend function of a class template

Templates that are friends of a template are a little different. Because they have parameters, the parameter list for the template class usually contains all the parameters to define the friend template. This is necessary to identify the instance of the friend template that is the friend of the particular instance of the original class template. However, the function template for the friend is only instantiated when you use it in your code. In the Figure 16-5, getBest() is a function template.

Figure 16-5.
figure 5figure 5

A function template that is a friend of a class template

Although each class template instance in Figure 16-5 could potentially have a unique friend template instance, this is not necessarily the case. If the class template has some parameters that aren’t parameters of the friend template, then a single instance of the friend template may service several instances of the class template.

Note that an ordinary class may have a class template or a function template declared as a friend. In this case, all instances of the template are friends of the class. With the example in Figure 16-6, every member function of every instance of the Thing template is a friend of the Box class because the template has been declared as a friend of the class.

Figure 16-6.
figure 6figure 6

A class template that is a friend of a class

The declaration in the Box class is a template for a friend declaration and this effectively generates a friend declaration for each class generated from the Thing template. If there are no instances of the Thing template then the Box class has no friends.

Class Templates with Nested Classes

A class can contain another class nested inside its definition. A class template definition can also contain a nested class or even a nested class template. A nested class template is independently parameterized, so inside another class template it creates a two-dimensional ability to generate classes. Dealing with templates inside templates is outside the scope of this book, but I’ll introduce aspects of a class template with a nested class.

Let’s take a particular example. Suppose you want to implement a stack, which is a “last in, first out” storage mechanism. A stack is illustrated in Figure 16-7. It works in a similar way to a plate stack in a self-service restaurant. It has two basic operations. A push operation adds an item at the top of a stack and a pop operation removes the item at the top of the stack. Ideally a stack implementation should be able to store objects of any type so this is a natural for a template.

Figure 16-7.
figure 7figure 7

The concept of a stack

The parameter for a Stack template is a type parameter that specifies the type of objects in the stack, so the initial template definition is going to be:

template <typename T>

class Stack

{

// Detail of the Stack definition...

};

If you want the stack’s capacity to grow automatically, you can’t use fixed storage for objects within the stack. One way of providing the ability to automatically grow and shrink the stack as objects are pushed on it or popped off it, is to implement the stack as a linked list. The nodes in the linked list can be created in the free store, and the stack only needs to remember the node at the top of the stack. This is illustrated in Figure 16-8.

Figure 16-8.
figure 8figure 8

A stack as a linked list

When you create an empty stack, the pointer to the head of the list is nullptr, so you can use the fact that it doesn’t contain any Node objects as an indicator that the stack is empty. Of course, only the Stack object needs access to the Node objects that are in the stack. The Node objects are just internal objects used to encapsulate the objects that are stored in the stack so there’s no need for anyone outside the Stack class to know that type Node exists.

A nested class that defines nodes in the list is required in each instance of the Stack template, and because a node must hold an object of type T, the Stack template parameter type, you can define it as a nested class in terms of T. We can add this to the initial outline of the Stack template:

template <typename T>

class Stack

{

private:

// Nested class

class Node

{

public:

T* pItem {};                           // Pointer to object stored

Node* pNext {};                        // Pointer to next node

Node(T& item) : pItem {&item} {}       // Create a node from an object

};

// Rest of the Stack class definition...

};

The Nodeclass is declared as private so we can afford to make all its members public so that they’re directly accessible from function members of the Stack template. Objects of type T are the responsibility of the user of the Stack class so only a pointer to an object of type T is stored in a Node object. The constructor is used when an object is pushed onto the stack. The parameter to the constructor is a reference to an object of type T and the address of this object is used to initialize the pItem member of the new Node object. The rest of the Stack class template to support the linked list of Node objects shown in Figure 16-8 is:

template <typename T>

class Stack

{

private:

// Nested Node class definition as before...

Node* pHead {};                            // Points to the top of the stack

void copy(const Stack& stack);             // Helper to copy a stack

void freeMemory();                         // Helper to release free store memory

public:

Stack() = default;                         // Default constructor

Stack(const Stack& stack);                 // Copy constructor

∼Stack();                                  // Destructor

Stack& operator=(const Stack& stack);      // Assignment operator

void push(T& item);                        // Push an object onto the stack

T& pop();                                  // Pop an object off the stack

bool isEmpty() {return !pHead;}            // Empty test

};

As I explained earlier, a Stack object only needs to “remember” the top node so it has only one data member, pHead, of type Node*. There’s a default constructor, a copy constructor, a destructor, and an assignment operator function. The destructor is essential because nodes will be created dynamically using new and the addresses stored in raw pointers. The push() and pop() members transfer objects to and from the stack and the isEmpty() function returns true if the Stack object is empty. The private copy() member of the Stack class will be used internally in the copy constructor and the assignment operator function to carry out operations that are common to both; this will reduce the number of lines of code necessary and the size of the executable. Similarly, the private freeMemory() function is a helper that will be used by the destructor and the assignment operator function. We just need the templates for the member functions of the Stack template to complete the implementation.

Function Templates for Stack Members

I’ll start with the definition of the template for the copy() member:

template <typename T>

void Stack<T>::copy(const Stack& stack)

{

if(stack.pHead)

{

pHead = new Node {*stack.pHead};           // Copy the top node of the original

Node* pOldNode {stack.pHead};              // Points to the top node of the original

Node* pNewNode {pHead};                    // Points to the node in the new stack

while(pOldNode = pOldNode->pNext)          // If it's nullptr, it's the last node

{

pNewNode->pNext = new Node {*pOldNode};  // Duplicate it

pNewNode = pNewNode->pNext;              // Move to the node just created

}

}

}

This copies the stack represented by the stack argument to the current Stack object, which is assumed to be empty. It does this by replicating pHead for the argument object, then walking through the sequence of Node objects, copying them one by one. The process ends when the Node object with a null pNext member has been copied.

The freeMemory() helper function will release the heap memory for all Node objects belonging to the current Stack object:

template <typename T>

void Stack<T>::freeMemory()

{

Node* pTemp {};

while(pHead)

{                                            // While current pointer is not null

pTemp = pHead->pNext;                      // Get the pointer to the next

delete pHead;                              // Delete the current

pHead = pTemp;                             // Make the next current

}

}

This uses a temporary pointer to a Node object to hold the address stored in the pNext member of a Node object before the object is deleted from the free store. At the end of the while loop, all Node objects belonging to the current Stack object will have been deleted and pHead will be nullptr.

The default constructor is defined within the template as the default that the compiler should supply. The copy constructor must replicate a Stack<T> object, which can be done by walking through the nodes and copying them, which is exactly what the copy() function does. This makes the template for the copy constructor trivial:

template <typename T>

Stack<T>::Stack(const Stack& stack)

{

copy(stack);

}

The assignment operator is similar to the copy constructor, but two extra things must be done. First, the function must check to see whether or not the objects involved are identical. Second, it must release memory for nodes in the object on the left of the assignment before copying the object that is the right operand. Here’s the template to define the assignment operator so that it works in this way:

template <typename T>

Stack<T>& Stack<T>::operator=(const Stack& stack)

{

if (this != &stack)                          // If objects are not identical

{

freeMemory();                              // Release memory for nodes in lhs

copy(stack);                               // Copy rhs to lhs

}

return *this                                 // Return the left object

}

Using the helper functions, the implementation becomes very simple. If the operands are the same, it’s only necessary to return the object. If the objects are different, the first step is to delete all the nodes for the left-hand object, which is done by calling freeMemory(), then replace them with copies of the nodes from the right operand by calling copy().

The code for the destructor template just needs to call freeMemory():

template <typename T>

Stack<T>::∼Stack()

{

freeMemory();

}

The template for the push() operation is very easy:

template <typename T>

void Stack<T>::push(T& item)

{

Node* pNode {new Node(item)};                // Create the new node

pNode->pNext = pHead;                        // Point to the old top node

pHead = pNode;                               // Make the new node the top

}

The Node object encapsulating item is created by passing the reference to the Node constructor. The pNext member of the new node needs to point to the node that was previously at the top. The new Node object then becomes the top of the stack so its address is stored in pHead.

The pop() operation is slightly more work because you must delete the top node:

template <typename T>

T& Stack<T>::pop()

{

T* pItem {pHead->pItem};                     // Get pointer to the top node object

if(!pItem)                                   // If it's empty

throw std::logic_error {"Stack empty"};    // Pop is not valid so throw exception

Node* pTemp {pHead};                         // Save address of top node

pHead = pHead->pNext;                        // Make next node the top

delete pTemp;                                // Delete the previous top node

return *pItem;                               // Return the top object

}

It is possible that there could be a pop operation on an empty stack. Because the function returns a reference, you can’t signal an error through the return value, so you have to throw an exception in this case. After storing the pointer to the object in the top node in the local pItem variable, the function deletes the top node, promotes the next node to the top, and returns a reference to the object.

That’s all the templates you need to define the stack. If you gather all the templates into a header file, Stacks.h, you can try it out with the following code:

// Ex16_04.cpp

// Using a stack defined by nested class templates

#include "Stacks.h"

#include <iostream>

#include <string>

using std::string;

int main()

{

const char* words[] {"The", "quick", "brown", "fox", "jumps"};

Stack<const char*> wordStack;             // A stack of C-style strings

for (size_t i {}; i < sizeof(words)/sizeof(words[0]) ; ++i)

wordStack.push(words[i]);

Stack<const char*> newStack {wordStack};  // Create a copy of the stack

// Display the words in reverse order

while(!newStack.isEmpty())

std::cout << newStack.pop() << " ";

std::cout << std::endl;

// Reverse wordStack onto newStack

while(!wordStack.isEmpty())

newStack.push(wordStack.pop());

// Display the words in original order

while(!newStack.isEmpty())

std::cout << newStack.pop() << " ";

std::cout << std::endl;

std::cout << std::endl << "Enter a line of text:" << std::endl;

string text;

std::getline(std::cin, text);             // Read a line into the string object

Stack<const char> characters;             // A stack for characters

for (size_t i {}; i < text.length(); ++i)

characters.push(text[i]);               // Push the string characters onto the stack

std::cout << std::endl;

while(!characters.isEmpty())

std::cout << characters.pop();          // Pop the characters off the stack

std::cout << std::endl;

}

Here’s an example of the output:

jumps fox brown quick The

The quick brown fox jumps

Enter a line of text:

Never test for errors that you don't know how to handle.

.eldnah ot woh wonk t'nod uoy taht srorre rof tset reveN

You first define an array of five objects that are null-terminated strings, initialized with the words shown. Then you define an empty Stack object that can store const char* objects. The for loop then pushes the array elements onto the stack. The first word from the array will be at the bottom of the wordStack stack and the last word at the top. You create a copy of wordStack as newStack to exercise the copy constructor.

In the next while loop, you display the words in newStack in reverse order by popping them off the stack and outputting them in a while loop. The loop continues until isEmpty() returns false. Using the isEmpty() function member is a safe way of getting the complete contents of a stack. newStack is empty by the end of the loop, but you still have the original in wordStack.

The next while loop retrieves the words from wordStack and pops them onto newStack. The pop and push operations are combined in a single statement, where the object returned by pop() for wordStack is the argument for push() for newStack(). At the end of this loop, wordStack is empty and newStack contains the words in their original sequence — with the first word at the top of the stack. You then output the words by popping them off newStack, so at the end of this loop, both stacks are empty:

The next part of main() reads a line of text into a string object using the getline() function and then creates a stack to store characters:

Stack<const char> characters;             // A stack for characters

This creates a new instance of the Stack template, Stack<const char>, and a new instance of the constructor for this type of stack. At this point, the program contains two classes from the Stack template each with a nested Node class.

You peel off the characters from text and push them onto the new stack in a for loop. The length() function of the text object is used to determine when the loop ends. Finally the input string is output in reverse by popping the characters off the stack. You can see from the output that my input was not even slightly palindromic, but you could try, “Ned, I am a maiden” or even “Are we not drawn onward, we few, drawn onward to new era.”

Summary

If you understand how class templates are defined and used, you’ll find it easy to understand and apply the capabilities of the Standard Template Library. The ability to define class templates is also a powerful augmentation of the basic language facilities for defining classes. The essential points I’ve discussed in this chapter include:

  • A class template defines a family of class types.

  • An instance of a class template is a class definition that generated by the compiler from the template using a set of template arguments that you specify in your code.

  • An implicit instantiation of a class template arises out of a definition for an object of a class template type.

  • An explicit instantiation of a class template defines a class for a given set of arguments for the template parameters.

  • An argument corresponding to a type parameter in a class template can be a fundamental type, a class type, a pointer type, or a reference type.

  • The type of a non-type parameter can be an integral or enumeration type, a pointer type, or a reference type.

  • A partial specialization of a class template defines a new template that is to be used for a specific, restricted subset of the arguments for the original template.

  • A complete specialization of a class template defines a new template for a specific, complete set of parameter arguments for the original template.

  • A friend of a class template can be a function, a class, a function template, or a class template.

  • An ordinary class can declare a class template or a function template as a friend.

EXERCISES

The following exercises enable you to try out what you’ve learned in this chapter. If you get stuck, look back over the chapter for help. If you’re still stuck after that, you can download the solutions from the Apress website ( www.apress.com/source-code/ ), but that really should be a last resort.

  • Exercise 16-1. Define a template for one-dimensional sparse arrays that will store objects of any type so that only the elements stored in the array occupy memory. The potential number of elements that can be stored by an instance of the template should be unlimited. The template might be used to define a sparse array containing pointers to elements of type double with the following statement:

SparseArray<double> values;

Define the subscript operator for the template so that element values can be retrieved and set just like in a normal array. If an element doesn’t exist at an index position, the subscript operator should return an object created by the default constructor for the object class. Exercise the template with a main() function that stores 20 random element values of type int at random index positions within the range 32 to 212 in a sparse array with an index range from 0 to 499, and output the values of element that exist along with their index positions.

Exercise 16-2. Define a template for a linked list type that allows the list to be traversed backward from the end of the list, as well as forward from the beginning. Apply the template in a program that stores individual words from some arbitrary prose or poetry as std::string objects in a linked list, and then displays them five to a line in sequence and in reverse order.

Exercise 16-3. Use the linked list and sparse array templates to produce a program that stores words from a prose or poetry sample in a sparse array of up to 26 linked lists, where each list contains words that have the same initial letter. Output the words, starting each group with a given initial letter on a new line. (Remember to leave a space between successive > characters when specifying template arguments—otherwise, >> will be interpreted as a shift right operator.)

Exercise 16-4. Add an insert() function to the SparseArray template that adds an element following the last element in the array. Use this function and a SparseArray instance that has elements that are SparseArray objects storing string objects to perform the same task as the previous exercise.