14. Templates

Objectives

In this chapter you’ll learn:

Image  To use function templates to conveniently create a group of related (overloaded) functions.

Image  To distinguish between function templates and function-template specializations.

Image  To use class templates to create a group of related types.

Image  To distinguish between class templates and class-template specializations.

Image  To overload function templates.

Image  To understand the relationships among templates, friends, inheritance and static members.

Behind that outside pattern the dim shapes get clearer every day.

It is always the same shape, only very numerous.

Charlotte Perkins Gilman

Every man of genius sees the world at a different angle from his fellows.

Havelock Ellis

...our special individuality, as distinguished from our generic humanity.

Oliver Wendell Holmes, Sr

Outline

14.1 Introduction

In this chapter, we discuss one of C++’s more powerful software reuse features, namely templates. Function templates and class templates enable programmers to specify, with a single code segment, an entire range of related (overloaded) functions—called function-template specializations—or an entire range of related classes—called class-template specializations. This technique is called generic programming.

We might write a single function template for an array-sort function, then have C++ generate separate function-template specializations that will sort int arrays, float arrays, string arrays and so on. We introduced function templates in Chapter 6. We present an additional discussion and example in this chapter.

We might write a single class template for a stack class, then have C++ generate separate class-template specializations, such as a stack-of-int class, a stack-of-float class, a stack-of-string class and so on.

Note the distinction between templates and template specializations: Function templates and class templates are like stencils out of which we trace shapes; function-template specializations and class-template specializations are like the separate tracings that all have the same shape, but could, for example, be drawn in different colors.

In this chapter, we present a function template and a class template. We also consider the relationships between templates and other C++ features, such as overloading, inheritance, friends and static members. The design and details of the template mechanisms discussed here are based on the work of Bjarne Stroustrup as presented in his paper, Parameterized Types for C++, and as published in the Proceedings of the USENIX C++ Conference held in Denver, Colorado, in October 1988.

This chapter is only an introduction to templates. Chapter 20, Standard Template Library (STL), presents an in-depth treatment of the template container classes, iterators and algorithms of the STL. Chapter 20 contains dozens of live-code template-based examples illustrating more sophisticated template-programming techniques than those used here.

Software Engineering Observation 14.1

Software Engineering Observation 14.1

Most C++ compilers require the complete definition of a template to appear in the client source-code file that uses the template. For this reason and for reusability, templates are often defined in header files, which are then #included into the appropriate client source-code files. For class templates, this means that the member functions are also defined in the header file.

14.2 Function Templates

Overloaded functions normally perform similar or identical operations on different types of data. If the operations are identical for each type, they can be expressed more compactly and conveniently using function templates. Initially, you write a single function-template definition. Based on the argument types provided explicitly or inferred from calls to this function, the compiler generates separate source-code functions (i.e., function-template specializations) to handle each function call appropriately. In C, this task can be performed using macros created with the preprocessor directive #define (see Appendix D, Preprocessor). However, macros can have serious side effects and do not enable the compiler to perform type checking. Function templates provide a compact solution, like macros, but enable full type checking.

Error-Prevention Tip 14.1

Error-Prevention Tip 14.1

Function templates, like macros, enable software reuse. Unlike macros, function templates help eliminate many types of errors through the scrutiny of full C++ type checking.

All function-template definitions begin with keyword template followed by a list of template parameters to the function template enclosed in angle brackets (< and >); each template parameter that represents a type must be preceded by either of the interchangeable keywords class or typename, as in

templatetypename T >

or

templateclass ElementType >

or

templatetypename BorderType, typename FillType >

The type template parameters of a function-template definition are used to specify the types of the arguments to the function, to specify the return type of the function and to declare variables within the function. The function definition follows and appears like any other function definition. Note that keywords typename and class used to specify function-template parameters actually mean “any built-in type or user-defined type.”

Common Programming Error 14.1

Common Programming Error 14.1

Not placing keyword class or keyword typename before each type template parameter of a function template is a syntax error.

Example: Function Template printArray

Let us examine function template printArray in Fig. 14.1, lines 8–15. Function template printArray declares (line 8) a single template parameter T (T can be any valid identifier) for the type of the array to be printed by function printArray; T is referred to as a type template parameter, or type parameter. You’ll see nontype template parameters in Section 14.5.

Fig 14.1 Function-template specializations of function template printArray.

 1   // Fig. 14.1: fig14_01.cpp
 2   // Using template functions.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6
 7   // function template printArray definition
 8   templatetypename T >
 9   void printArray( const T * const array, int count )
10   {
11      for ( int i = 0; i < count; i++ )
12         cout << array[ i ] << " ";
13
14      cout << endl;
15   } // end function template printArray
16
17   int main()
18   {
19      const int ACOUNT = 5// size of array a
20      const int BCOUNT = 7// size of array b
21      const int CCOUNT = 6// size of array c
22
23      int a[ ACOUNT ] = { 1, 2, 3, 4, 5 };
24      double b[ BCOUNT ] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7 };
25      char c[ CCOUNT ] = "HELLO"// 6th position for null
26
27      cout << "Array a contains:" << endl;
28

29      // call integer function-template specialization
30      printArray( a, ACOUNT );                        
31
32      cout << "Array b contains:" << endl;
33
34      // call double function-template specialization
35      printArray( b, BCOUNT );                       
36
37      cout << "Array c contains:" << endl;
38
39      // call character function-template specialization
40      printArray( c, CCOUNT );                          
41      return 0;
42   } // end main

Array a contains:
1 2 3 4 5
Array b contains:
1.1 2.2 3.3 4.4 5.5 6.6 7.7
Array c contains:
H E L L O

When the compiler detects a printArray function invocation in the client program (e.g., lines 30, 35 and 40), the compiler uses its overload resolution capabilities to find a definition of function printArray that best matches the function call. In this case, the only printArray function with the appropriate number of parameters is the printArray function template (lines 8–15). Consider the function call at line 30. The compiler compares the type of printArray’s first argument (int * at line 30) to the printArray function template’s first parameter (const T * const at line 9) and deduces that replacing the type parameter T with int would make the argument consistent with the parameter. Then, the compiler substitutes int for T throughout the template definition and compiles a printArray specialization that can display an array of int values. In Fig. 14.1, the compiler creates three printArray specializations—one that expects an int array, one that expects a double array and one that expects a char array. For example, the function-template specialization for type int is

void printArray( const int * const array, int count )
{
   for ( int i = 0; i < count; i++ )
      cout << array[ i ] << " ";

   cout << endl;
// end function printArray

The name of a template parameter can be declared only once in the template parameter list of a template header but can be used repeatedly in the function’s header and body. Template parameter names among function templates need not be unique.

Figure 14.1 demonstrates function template printArray (lines 8–15). The program begins by declaring five-element int array a, seven-element double array b and six-element char array c (lines 23–25, respectively). Then, the program outputs each array by calling printArray—once with a first argument a of type int * (line 30), once with a first argument b of type double * (line 35) and once with a first argument c of type char * (line 40). The call in line 30, for example, causes the compiler to infer that T is int and to instantiate a printArray function-template specialization, for which type parameter T is int. The call in line 35 causes the compiler to infer that T is double and to instantiate a second printArray function-template specialization, for which type parameter T is double. The call in line 40 causes the compiler to infer that T is char and to instantiate a third printArray function-template specialization, for which type parameter T is char. It is important to note that if T (line 8) represents a user-defined type (which it does not in Fig. 14.1), there must be an overloaded stream insertion operator for that type; otherwise, the first stream insertion operator in line 12 will not compile.

Common Programming Error 14.2

Common Programming Error 14.2

If a template is invoked with a user-defined type, and if that template uses functions or operators (e.g., ==, +, <=) with objects of that class type, then those functions and operators must be overloaded for the user-defined type. Forgetting to overload such operators causes compilation errors.

In this example, the template mechanism saves you from having to write three separate overloaded functions with prototypes

void printArray( const int *, int );
void printArray( const double *, int );
void printArray( const char *, int );

that all use the same code, except for type T (as used in line 9).

Performance Tip 14.1

Performance Tip 14.1

Although templates offer software-reusability benefits, remember that multiple function-template specializations and class-template specializations are instantiated in a program (at compile time), despite the fact that the templates are written only once. These copies can consume considerable memory. This is not normally an issue, though, because the code generated by the template is the same size as the code you would have written to produce the separate overloaded functions.

14.3 Overloading Function Templates

Function templates and overloading are intimately related. The function-template specializations generated from a function template all have the same name, so the compiler uses overloading resolution to invoke the proper function.

A function template may be overloaded in several ways. We can provide other function templates that specify the same function name but different function parameters. For example, function template printArray of Fig. 14.1 could be overloaded with another printArray function template with additional parameters lowSubscript and highSubscript to specify the portion of the array to output.

A function template also can be overloaded by providing nontemplate functions with the same function name but different function arguments. For example, function template printArray of Fig. 14.1 could be overloaded with a nontemplate version that specifically prints an array of character strings in neat, tabular format.

The compiler performs a matching process to determine what function to call when a function is invoked. First, the compiler finds all function templates that match the function named in the function call and creates specializations based on the arguments in the function call. Then, the compiler finds all the ordinary functions that match the function named in the function call. If one of the ordinary functions or function-template specializations is the best match for the function call, that ordinary function or specialization is used. If an ordinary function and a specialization are equally good matches for the function call, then the ordinary function is used. Otherwise, if there are multiple matches for the function call, the compiler considers the call to be ambiguous and the compiler generates an error message.

Common Programming Error 14.3

Common Programming Error 14.3

A compilation error occurs if no matching function definition can be found for a particular function call or of there are multiple matches that the compiler considers ambigous.

14.4 Class Templates

It is possible to understand the concept of a “stack” (a data structure into which we insert items at the top and retrieve those items in last-in, first-out order) independent of the type of the items being placed in the stack. However, to instantiate a stack, a data type must be specified. This creates a wonderful opportunity for software reusability. We need the means for describing the notion of a stack generically and instantiating classes that are type-specific versions of this generic stack class. C++ provides this capability through class templates.

Software Engineering Observation 14.2

Software Engineering Observation 14.2

Class templates encourage software reusability by enabling type-specific versions of generic classes to be instantiated.

Class templates are called parameterized types, because they require one or more type parameters to specify how to customize a “generic class” template to form a class-template specialization.

To produce a variety of class-template specializations you write only one class-template definition. Each time an additional class-template specialization is needed, you use a concise, simple notation, and the compiler writes the source code for the specialization you require. One Stack class template, for example, could thus become the basis for creating many Stack classes (such as “Stack of double,” “Stack of int,” “Stack of char,” “Stack of Employee,” etc.) used in a program.

Creating Class Template Stack< T >

Note the Stack class-template definition in Fig. 14.2. It looks like a conventional class definition, except that it is preceded by the header (line 6)

templatetypename T >

to specify a class-template definition with type parameter T which acts as a placeholder for the type of the Stack class to be created. You need not specifically use identifier T—any valid identifier can be used. The type of element to be stored on this Stack is mentioned generically as T throughout the Stack class header and member-function definitions. In a moment, we show how T becomes associated with a specific type, such as double or int. Due to the way this class template is designed, there are two constraints for nonfundamental data types used with this Stack—they must have a default constructor (for use in line 44 to create the array that stores the stack elements), and they must support the assignment operator (lines 56 and 70).

Fig 14.2 Class template Stack.

 1   // Fig. 14.2: Stack.h
 2   // Stack class template.
 3   #ifndef STACK_H
 4   #define STACK_H
 5
 6   templatetypename T >
 7   class Stack           
 8   {
 9   public:
10      Stackint = 10 ); // default constructor (Stack size 10)
11
12      // destructor
13      ~Stack()
14      {
15         delete [] stackPtr; // deallocate internal space for Stack
16      } // end ~Stack destructor
17
18      bool push( const T & ); // push an element onto the Stack
19      bool pop( T & ); // pop an element off the Stack
20
21      // determine whether Stack is empty
22      bool isEmpty() const
23      {
24         return top == -1;
25      } // end function isEmpty
26
27      // determine whether Stack is full
28      bool isFull() const
29      {
30         return top == size - 1;
31      } // end function isFull
32
33   private:
34      int size; // # of elements in the Stack
35      int top; // location of the top element (-1 means empty)
36      T *stackPtr; // pointer to internal representation of the Stack
37   }; // end class template Stack
38
39   // constructor template
40   template< typename T >    
41   Stack< T >::Stack( int s )
42      : size( s > 0 ? s : 10 ), // validate size
43        top( -1 ), // Stack initially empty
44        stackPtr( new T[ size ] ) // allocate memory for elements
45   {
46      // empty body
47   } // end Stack constructor template
48
49   // push element onto Stack;
50   // if successful, return true; otherwise, return false
51   template< typename T >                     
52   bool Stack< T >::push( const T &pushValue )
53   {

54     if ( !isFull() )
55      {
56         stackPtr[ ++top ] = pushValue; // place item on Stack
57         return true// push successful
58      } // end if
59
60      return false;  // push unsuccessful
61   } // end function template push
62
63   // pop element off Stack;
64   // if successful, return true; otherwise, return false
65   templatetypename T >             
66   bool Stack< T >::pop( T &popValue )
67   {
68      if ( !isEmpty() )
69      {
70         popValue = stackPtr[ top-- ]; // remove item from Stack
71         return true// pop successful
72      } // end if
73
74      return false; // pop unsuccessful
75   } // end function template pop
76
77   #endif

The member-function definitions of a class template are function templates. The member-function definitions that appear outside the class template definition each begin with the header

templatetypename T >

(lines 40, 51 and 65). Thus, each definition resembles a conventional function definition, except that the Stack element type always is listed generically as type parameter T. The binary scope resolution operator is used with the class-template name Stack< T > (lines 41, 52 and 66) to tie each member-function definition to the class template’s scope. In this case, the generic class name is Stack< T >. When doubleStack is instantiated as type Stack< double >, the Stack constructor function-template specialization uses new to create an array of elements of type double to represent the stack (line 44). The statement

stackPtr = new T[ size ];

in the Stack class-template definition is generated by the compiler in the class-template specialization Stack< double > as

stackPtr = new double[ size ];

Creating a Driver to Test Class Template Stack< T >

Now, let us consider the driver (Fig. 14.3) that uses the Stack class template. The driver begins by instantiating object doubleStack of size 5 (line 11). This object is declared to be of class Stack< double > (pronounced “Stack of double”). The compiler associates type double with type parameter T in the class template to produce the source code for a Stack class of type double. Although templates offer software-reusability benefits, remember that multiple class-template specializations are instantiated in a program (at compile time), even though the template is written only once.

Fig 14.3 Class template Stack test program.

 1   // Fig. 14.3: fig14_03.cpp
 2   // Stack class template test program.
 3   #include <iostream>
 4   using std::cout;
 5   using std::endl;
 6
 7   #include "Stack.h" // Stack class template definition
 8
 9   int main()
10   {
11      Stack< double > doubleStack( 5 ); // size 5
12      double doubleValue = 1.1;
13
14      cout << "Pushing elements onto doubleStack ";
15
16      // push 5 doubles onto doubleStack
17      while ( doubleStack.push( doubleValue ) )
18      {
19         cout << doubleValue << ' ' ;
20         doubleValue += 1.1;
21      } // end while
22
23      cout << " Stack is full. Cannot push " << doubleValue
24         << " Popping elements from doubleStack ";
25
26      // pop elements from doubleStack
27      while ( doubleStack.pop( doubleValue ) )
28         cout << doubleValue << ' ';
29
30      cout << " Stack is empty. Cannot pop ";
31
32      Stack< int > intStack; // default size 10
33      int intValue = 1;
34      cout << " Pushing elements onto intStack ";
35
36      // push 10 integers onto intStack
37      while ( intStack.push( intValue )  )
38      {
39         cout << intValue++ << ' ';
40      } // end while
41
42      cout << " Stack is full. Cannot push " << intValue
43         << " Popping elements from intStack ";
44
45      // pop elements from intStack
46      while ( intStack.pop( intValue )  )
47         cout << intValue << ' ';
48
49      cout << " Stack is empty. Cannot pop" << endl;
50      return 0;
51   } // end main

 Pushing elements onto doubleStack
 1.1 2.2 3.3 4.4 5.5
 Stack is full. Cannot push 6.6

 Popping elements from doubleStack
 5.5 4.4 3.3 2.2 1.1
 Stack is empty. Cannot pop

 Pushing elements onto intStack
 1 2 3 4 5 6 7 8 9 10
 Stack is full. Cannot push 11

 Popping elements from intStack
 10 9 8 7 6 5 4 3 2 1
 Stack is empty. Cannot pop

Lines 17–21 invoke push to place the double values 1.1, 2.2, 3.3, 4.4 and 5.5 onto doubleStack. The while loop terminates when the driver attempts to push a sixth value onto doubleStack (which is full, because it holds a maximum of five elements). Note that function push returns false when it is unable to push a value onto the stack.1

Lines 27–28 invoke pop in a while loop to remove the five values from the stack (note, in the output of Fig. 14.3, that the values do pop off in last-in, first-out order). When the driver attempts to pop a sixth value, the doubleStack is empty, so the pop loop terminates.

Line 32 instantiates integer stack intStack with the declaration

Stack< int > intStack;

(pronounced “intStack is a Stack of int”). Because no size is specified, the size defaults to 10 as specified in the default constructor (Fig. 14.2, line 10). Lines 37–40 loop and invoke push to place values onto intStack until it is full, then lines 46–47 loop and invoke pop to remove values from intStack until it is empty. Once again, notice in the output that the values pop off in last-in, first-out order.

Creating Function Templates to Test Class Template Stack< T >

Notice that the code in function main of Fig. 14.3 is almost identical for both the doubleStack manipulations in lines 11–30 and the intStack manipulations in lines 32–50. This presents another opportunity to use a function template. Figure 14.4 defines function template testStack (lines 14–38) to perform the same tasks as main in Fig. 14.3push a series of values onto a Stack< T > and pop the values off a Stack< T >. Function template testStack uses template parameter T (specified at line 14) to represent the data type stored in the Stack< T >. The function template takes four arguments (lines 16–19)—a reference to an object of type Stack< T >, a value of type T that will be the first value pushed onto the Stack< T >, a value of type T used to increment the values pushed onto the Stack< T > and a string that represents the name of the Stack< T > object for output purposes. Function main (lines 40–49) instantiates an object of type Stack< double > called doubleStack (line 42) and an object of type Stack< int > called intStack (line 43) and uses these objects in lines 45 and 46. The compiler infers the type of T for testStack from the type used to instantiate the function’s first argument (i.e., the type used to instantiate doubleStack or intStack). The output of Fig. 14.4 precisely matches the output of Fig. 14.3.

Fig 14.4 Passing a Stack template object to a function template.

 1   // Fig. 14.4: fig14_04.cpp
 2   // Stack class template test program. Function main uses a
 3   // function template to manipulate objects of type Stack< T >.
 4   #include <iostream>
 5   using std::cout;
 6   using std::endl;
 7
 8   #include <string>
 9   using std::string;
10
11   #include "Stack.h" // Stack class template definition
12

13   // function template to manipulate Stack< T >               
14   templatetypename T >                                      
15   void testStack(                                             
16      Stack< T > &theStack, // reference to Stack< T >         
17      T value, // initial value to push                        
18      T increment, // increment for subsequent values          
19      const string stackName ) // name of the Stack< T > object
20   {                                                           
21      cout << " Pushing elements onto " << stackName << ' '
22                                                               
23      // push element onto Stack                               
24      while ( theStack.push( value ) )                         
25      {                                                        
26         cout << value << ' ';                                 
27         value += increment;                                   
28      }  // end while                                          
29                                                               
30      cout << " Stack is full. Cannot push " << value         
31         << " Popping elements from " << stackName << ' '
32                                                               
33      // pop elements from Stack                               
34      while ( theStack.pop( value ) )                          
35         cout << value << ' ';                                 
36                                                               
37      cout << " Stack is empty. Cannot pop" << endl;          
38   } // end function template testStack                        
39
40   int main()
41   {
42      Stack< double > doubleStack( 5 ); // size 5
43      Stack< int > intStack; // default size 10
44
45      testStack( doubleStack, 1.1, 1.1"doubleStack" );
46      testStack( intStack, 1, 1"intStack" );          
47
48      return 0;
49   } // end main

Pushing elements onto doubleStack
1.1 2.2 3.3 4.4 5.5
Stack is full. Cannot push 6.6

Popping elements from doubleStack
5.5 4.4 3.3 2.2 1.1
Stack is empty. Cannot pop

Pushing elements onto intStack
1 2 3 4 5 6 7 8 9 10
Stack is full. Cannot push 11

Popping elements from intStack
10 9 8 7 6 5 4 3 2 1
Stack is empty. Cannot pop

14.5 Nontype Parameters and Default Types for Class Templates

Class template Stack of Section 14.4 used only a type parameter in the template header (Fig. 14.2, line 6). It is also possible to use nontype template parameters or nontype parameters, which can have default arguments and are treated as consts. For example, the template header could be modified to take an int elements parameter as follows:

templatetypename T, int elements > // nontype parameter elements

Then, a declaration such as

Stack< double100 > mostRecentSalesFigures;

could be used to instantiate (at compile time) a 100-element Stack class-template specialization of double values named mostRecentSalesFigures; this class-template specialization would be of type Stack< double, 100 >. The class header then might contain a private data member with an array declaration such as

T stackHolder[ elements ]; // array to hold Stack contents

In addition, a type parameter can specify a default type. For example,

templatetypename T = string > // defaults to type string

might specify that a Stack contains string objects by default. Then, a declaration such as

Stack<> jobDescriptions;

could be used to instantiate a Stack class-template specialization of strings named jobDescriptions; this class-template specialization would be of type Stack< string >. Default type parameters must be the rightmost (trailing) parameters in a template’s type-parameter list. When one is instantiating a class with two or more default types, if an omitted type is not the rightmost type parameter in the type-parameter list, then all type parameters to the right of that type also must be omitted.

Performance Tip 14.2

Performance Tip 14.2

When appropriate, specify the size of a container class (such as an array class or a stack class) at compile time (possibly through a nontype template parameter). This eliminates the execution-time overhead of using new to create the space dynamically.

Software Engineering Observation 14.3

Software Engineering Observation 14.3

Specifying the size of a container at compile time avoids the potentially fatal execution-time error if new is unable to obtain the needed memory.

In some cases, it may not be possible to use a particular type with a class template. For example, the Stack template of Fig. 14.2 requires that user-defined types that will be stored in a Stack must provide a default constructor and an assignment operator. If a particular user-defined type will not work with our Stack template or requires customized processing, you can define an explicit specialization of the class template for a particular type. Let’s assume we want to create an explicit specialization Stack for Employee objects. To do this, form a new class with the name Stack< Employee > as follows:

template<>
class Stack< Employee >
{
   // body of class definition
};

Note that the Stack< Employee > explicit specialization is a complete replacement for the Stack class template that is specific to type Employee—it does not use anything from the original class template and can even have different members.

14.6 Notes on Templates and Inheritance

Templates and inheritance relate in several ways:

•     A class template can be derived from a class-template specialization.

•     A class template can be derived from a nontemplate class.

•     A class-template specialization can be derived from a class-template specialization.

•     A nontemplate class can be derived from a class-template specialization.

14.7 Notes on Templates and Friends

We have seen that functions and entire classes can be declared as friends of nontemplate classes. With class templates, friendship can be established between a class template and a global function, a member function of another class (possibly a class-template specialization), or even an entire class (possibly a class-template specialization).

Throughout this section, we assume that we have defined a class template for a class named X with a single type parameter T, as in:

templatetypename T > class X

Under this assumption, it is possible to make a function f1 a friend of every class-template specialization instantiated from the class template for class X. To do so, use a friendship declaration of the form

friend void f1();

For example, function f1 is a friend of X< double >, X< string > and X< Employee >, etc.

It is also possible to make a function f2 a friend of only a class-template specialization with the same type argument. To do so, use a friendship declaration of the form

friend void f2( X< T > & );

For example, if T is a float, function f2( X< float > & ) is a friend of class-template specialization X< float > but not a friend of class-template specification X< string >.

You can declare that a member function of another class is a friend of any class-template specialization generated from the class template. To do so, the friend declaration must qualify the name of the other class’s member function using the class name and the binary scope resolution operator, as in:

friend void A::f3();

The declaration makes member function f3 of class A a friend of every class-template specialization instantiated from the preceding class template. For example, function f3 of class A is a friend of X< double >, X< string > and X< Employee >, etc.

As with a global function, another class’s member function can be a friend of only a class-template specialization with the same type argument. A friendship declaration of the form

friend void C< T >::f4( X< T > & );

for a particular type T such as float makes member function

C< float >::f4( X< float > & )

a friend function of only class-template specialization X< float >.

In some cases, it is desirable to make an entire class’s set of member functions friends of a class template. In this case, a friend declaration of the form

friend class Y;

makes every member function of class Y a friend of every class-template specialization produced from the class template X.

Finally, it is possible to make all member functions of one class-template specialization friends of another class-template specialization with the same type argument. For example, a friend declaration of the form:

friend class Z< T >;

indicates that when a class-template specialization is instantiated with a particular type for T (such as float), all members of class Z< float > become friends of class-template specialization X< float >.

14.8 Notes on Templates and static Members

What about static data members? Recall that, with a nontemplate class, one copy of each static data member is shared among all objects of the class, and the static data member must be initialized at file scope.

Each class-template specialization instantiated from a class template has its own copy of each static data member of the class template; all objects of that specialization share that one static data member. In addition, as with static data members of nontemplate classes, static data members of class-template specializations must be defined and, if necessary, initialized at file scope. Each class-template specialization gets its own copy of the class template’s static member functions.

14.9 Wrap-Up

This chapter introduced one of C++’s most powerful features—templates. You learned how to use function templates to enable the compiler to produce a set of function-template specializations that represent a group of related overloaded functions. We also discussed how to overload a function template to create a specialized version of a function that handles a particular data type’s processing in a manner that differs from the other function-template specializations. Next, you learned about class templates and class-template specializations. You saw examples of how to use a class template to create a group of related types that each perform identical processing on different data types. Finally, you learned about some of the relationships among templates, friends, inheritance and static members.

In the next chapter, we discuss many of C++’s I/O capabilities and demonstrate several stream manipulators that perform various formatting tasks.

1. Class Stack (Fig. 14.2) provides the function isFull, which you can use to determine whether the stack is full before attempting a push operation. This would avoid the potential error of pushing onto a full stack. In Chapter 16, Exception Handling, if the operation cannot be completed, function push would “throw an exception.” You can write code to “catch” that exception, then decide how to handle it appropriately for the application. The same technique can be used with function pop when an attempt is made to pop an element from an empty stack.

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

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