In this chapter you’ll learn:
To use function templates to conveniently create a group of related (overloaded) functions.
To distinguish between function templates and function-template specializations.
To use class templates to create a group of related types.
To distinguish between class templates and class-template specializations.
To overload function templates.
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
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.
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 #include
d into the appropriate client source-code files. For class templates, this means that the member functions are also defined in the header file.
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.
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
template< typename T >
or
template< class ElementType >
or
template< typename 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.”
Not placing keyword class
or keyword typename
before each type template parameter of a function template is a syntax error.
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 template< typename 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.
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).
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.
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.
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.
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.
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)
template< typename 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 template< typename T >
7 class Stack
8 {
9 public:
10 Stack( int = 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 template< typename 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
template< typename 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
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
(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.
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.3—push
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 push
ed onto the Stack< T >
, a value of type T
used to increment the values push
ed 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 template< typename 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
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 const
s. For example, the template header could be modified to take an int elements
parameter as follows:
template< typename T, int elements > // nontype parameter elements
Then, a declaration such as
Stack< double, 100 > 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,
template< typename 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 string
s 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.
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.
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.
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.
We have seen that functions and entire classes can be declared as friend
s 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:
template< typename 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 >
.
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.
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.