CS202 Programming
Systems
Lecture Notes #11
Template Classes
Lecture
and Reference
Class Template: A parameterization of type dependencies that allows the compiler to automatically generate as many instances of a class as needed. A general purpose class template is called a primary class template, where all type and non-type dependencies are represented by identifiers rather than specific types or values.
Template Class: A particular instance of a general purpose abstraction.
Specialized Template Class: A customized instantiation of a class template defined for a particular set of type dependencies.
Partially Specialized Template Class: A parameterization of type dependencies that is more specialized than a primary class template but with some type dependencies remaining.
In Lecture 16, we introduced function templates and parametric polymorphism. Using function templates, the same source code is used to manipulate many different types of data. With class templates, we apply this same concept to classes. It allows us to create general purpose abstractions and use the same abstraction to apply to many different types of data. The class template is the definition of a parameterized family of abstractions. A primary class template is the most generalized with identifiers representing each of the type and non-type dependencies. A template class is a particular instance of this family.
Specialized class templates are customized versions of a template class that work for situations that don't fit the normal pattern. With specialized template classes, we can support such differences, promoting reuse as well as the ability to customize an implementation of an abstraction for particular data types. With a specialized class template we explicitly specify each of the type and non-type dependencies when we define the class. Partially specialized class templates are semi-customized versions of a template class where some of the type and non-type dependencies remain unspecified. Such specializations are commonly written to support pointers and other compound data types that behave differently than the built-in data types.
The following sections outline the objectives and application of class templates. This prepares us for the next step, where we will learn how to implement and use general purpose abstractions.
11.1 The Objectives of Class Templates
Class templates allow us to apply the concept of parametric polymorphism on a class-by-class basis. Their purpose is to isolate type dependencies for a particular class of objects. Using class templates, we shift the burden of creating duplicate classes to handle various combinations of data types to the compiler.
The purpose of a class template is much like that of a template function. The intent of a class template is to isolate within a class any type dependencies. Type dependencies can be found by looking for those types that cause a particular class to differ from another class responsible for the same kind of behavior. It allows us to have a family of closely related classes that vary only in the data type used by their objects, without having to write the code for each class.
11.2 The Application of Class Templates
Consider commonly used abstractions, such as stacks, queues, lists, or trees. Without class templates, these abstractions would need separate implementations designed for each type of data stored. We would need to implement an individual class for a stack of integers, another class for a stack of floats, and yet another class for a stack of characters. Once this code is designed, we would come to the realization that the operations to push and pop information are identical, except they manage different types of data. However, we still would be required to implement each version of these operations as separate member functions.
With class templates, we simply write one class and shift the burden of creating multiple instances of that class to the compiler. The compiler automatically generates as many instances of that class as is required to meet the client's demands without unnecessary code duplication. For example, if the client only uses an integer stack, only one instance of the class is generated. If the client uses an integer stack as well as a stack of real numbers, the compiler will generate two instances of the class: one specific for integers and another specific for floats.
Specialized (and partially specialized) template classes can be used to handle special cases that arise. For example, the code to support a stack would need to be different when storing integers, reals, or characters versus character strings to handle deep copy operations. By explicitly defining partial or completely specialized template classes, the programmer can customize their abstractions to handle special cases.
11.3 Implementing Class Templates
Implementing class templates is the final step in making our abstract data types general purpose. The compiler is responsible for generating instantiations of a class' source code depending on the client's requests. The syntax for implementing class templates is similar to that of defining and declaring classes themselves. We simply preface the class definition or declaration with the keyword template followed by a parameterized list of type dependencies.
The following sections step through the process of defining and declaring class templates. Once we have created general purpose abstractions, we will learn how clients can cause class templates to be implicitly and explicitly instantiated and how clients can specify type dependencies.
11.3.1 Class Template Definition
Class Template Definition: A class template definition is that place where the class template's interface is found.
Class Template Formal Arguments: The identifiers in a class template's definition that take on the data types specified by the client when the template class is instantiated.
Class templates follow the same definition rules as non-template classes except that we precede the class' header with a specification that this is a template class. We begin with the keyword template followed by the class template's formal argument list within angle brackets (< >). This is followed by the class interface.
The information placed after the keyword template is the template formal argument list. The template formal argument list consists of a comma separated list containing the identifiers for each formal argument in the list. There must be at least one formal argument specified. The scope of the formal arguments is that of the class template itself. That is, formal arguments are within class scope; they cannot be accessed from outside of the class.
Figure 11-1 illustrates the definition of a primary class template. Notice that its definition is very similar to that of a template function:

Figure 11-1: The Interface of a Primary Class Template
The name of a class template cannot be redeclared within the same scope (e.g., it cannot be used to declare some other template class, function, or object).
The template formal argument list can contain three different kinds of arguments: identifiers that represent type dependencies, identifiers that represent values, and identifiers that represent type dependencies based on a template class. Identifiers that represent type dependencies are template type formal arguments. Identifiers that represent values are non-type formal arguments. Identifiers that represent other template classes are template class formal arguments. The identifiers selected must not be the same as the class' identifier.
Practical Rule: The formal argument list of a class template cannot be empty. The name of a formal argument can appear only once as a type or non-type identifier in a template's formal argument list. However, a formal argument may be used from its point of declaration to specify the type of subsequent non-type values.
11.3.1.1 Steps to Specifying
a Class Template
Table 11-1 summarizes the steps necessary to specify a class template.
Steps to Specifying a Class
Template
Step 1: Determine what type dependencies exist.
Step 2: Start both the class's definition and declaration(s) with the template keyword. Start all member functions implemented outside of the class with the template keyword.
Step 3: Follow the template keyword by the identifiers that represent each type dependency, each non-type value, and each template class. Type dependencies must be preceded by the keyword class or typename and placed inside of angle brackets (< >). Non-type values must be preceded by their data type and optionally the keyword typename. Identifiers that represent other template classes must be preceded with the keyword template with their type dependencies specified.
Step 4: Supply the class interface, as normal, and end this with a semicolon. For member functions, follow this with the function's header and then its implementation inside of brackets. The identifiers specified in Step #3 can be used to build in type dependencies for locals, data members, arguments to member functions, and the return type of member functions.
Table 11-1: Steps to Specifying a Class Template
11.3.1.2 Template Type
Formal Arguments
The template's formal argument list allows us to identify the type dependencies within a general purpose abstraction. A type dependency allows the identifier listed to be substituted for the data type specified by the client at instantiation time. More than one identifier can be listed, each prefaced by the keyword class to indicate that this is a type dependency. Type dependencies can also be specified by preceding the identifier by the keyword typename. There is no semantic difference between using the keyword class and the keyword typename in this context.
template <class TYPE_identifier>
or,
template <typename TYPE_identifier>
Each type identifier must be prefaced by the keyword class or typename and can then be used as the data type for one or more data members, formal arguments to member functions, or the return type of member functions. They can also be used inside a member function's definition as the data type for local variables.
The template formal argument list may contain more than one identifier representing different type dependencies. When we have only one identifier listed, the class template depends on only that one type. When we have more than one type listed inside the angle brackets, the class template depends on more than one type. For example, the following is a template's formal argument list where there are two type dependencies:
template <class TYPE_ID1, class TYPE_ID2>
or,
template <typename TYPE_ID1, typename TYPE_ID2>
By convention, the identifiers selected as the type specifiers are written in all upper case. Each of the identifiers must be given a unique name. We can't use the same name twice in a class template's formal argument list. Obviously, from within the class' definition we can use the identifier as many times as desired to represent the data type for data members, formal arguments, local variables, as well as return types. The scope of the type identifiers is restricted to the scope of the class that immediately follows.
Only those identifiers that immediately follow the template's formal argument list can be used within the class. A previous template's type dependencies are meaningless. The type identifiers hide any identifiers previous used of the same name (i.e., such as globals). The type identifiers are restricted to the scope of the class that immediately follows.
A common programming error is to forget to place the keyword class or typename before every type dependencies in the template formal argument.
11.3.1.3 Non-type
Formal Arguments
We can also specify a non-type identifier in the template formal argument list. A non-type specifier allows the identifier listed to be substituted for a value specified by the client at instantiation time. More than one identifier can be listed, each prefaced by its data type. Non-type formal arguments may also be preceded by the keyword typename. For example, the following is a template's formal argument list where there is just one non-type identifier:
template <data_type nontype_identifier>
or,
template <typename data_type nontype_identifier>
The data type of non-type identifiers must be an integral type, an enumeration type, a pointer to an object, a reference to an object, a pointer to a function, a reference to a function, or a pointer to a member. They cannot be void or a floating point type.
Clients must specify the values for non-type identifiers explicitly inside of angle brackets when defining objects class templates. The following shows an example of how a client can specify non-type identifiers:
//function template declaration
template <char non_type, class TYPE_ID>
class t_class {
public:
TYPE_ID function(int TYPE_ID);
};
//client code
t_class <'\n',int> obj;
Using non-type formal arguments allows the client to specify at compile time some constant expression when defining an object, which can be used by the class to initialize constants, determine the size of statically allocated arrays, or any other initialization. For example, if the non-type identifier is declared to be an integral or enumeration type, then the client can specify an integral constant expression as the actual argument. If the non-type identifier is declared to be a pointer to an object, then the client can specify an address to a constant expression representing an object of that type. It is only when the non-type identifier is declared as a reference that the client must specify an lvalue expression instead of a temporary.
Non-type formal arguments that are not specified as references are used as a constants when a template class is instantiated. Therefore, it is illegal to attempt to change their value within the class' member functions. It is also illegal to take the address of non-type formal arguments that are not references. Think of them as constants; they cannot be modified or taken the address of. The following examples demonstrate legal as well as illegal non-type formal arguments passed by value:
template <char c> class class_name {...} //legal
template <class_name* ptr> class class_name {...} //legal
template <fctn_name* ptr> class class_name { ... } //legal
template <float f> class class_name {...} //illegal
template <float* ptr> class class_name {...} //legal
Non-type formal arguments that are specified as references must correspond to either an object or a function that has external linkage. This means that temporaries cannot be used by clients as the corresponding actual arguments. Only globals with external linkage can be used in such cases. C++ prohibits such non-type formal arguments from being initialized with static globals, locals, or even literals. The following examples demonstrate possible non-type formal arguments passed by reference:
template <class-name& obj> class class_name {...} //legal
template <fctn_name& fctn> class class_name {...} //legal
11.3.1.4 Template
Classes as Formal Arguments
A class template's formal argument list may include other template classes. This means that the type dependency is based on an instantiation of another abstraction. When this is the case, the identifier representing the type dependency is preceded by the keyword template, the specific type dependencies to be substituted supplied in angle brackets, and the keyword class:
template <template <actual_arguments> class_identifier>
When making use of this features, the template classes being used as formal arguments must be defined before the first use that causes an instantiation of the class template.
11.3.1.5 Scope of a
Class Template's Formal Argument
The scope of a class template's formal argument extends from the point of its declaration until the end of the class template. Once an identifier is declared, it can be used for subsequent template arguments without redeclaration. In fact, C++ prohibits the redeclaration of the template formal argument's identifiers in any scope nested within the template definition. This includes in the definition of member functions!
Because the scope of our template arguments is from the point of declaration to the end of the function, template arguments can be used as the data type for subsequent non-type arguments and as the default types for subsequent type arguments.
A template's formal arguments hide any identifiers of the same name in the enclosing scope. However, it does not hide any subsequent identifiers that may occur in the class template or member functions.
11.3.2 Class Template Declaration
Class Template Declaration: A class template declaration either defines or declares a class. A declaration specifies that the class is defined elsewhere.
C++ requires that we either define or declare class templates before using them. Class templates must be declared if the class is to be used before it is defined, either in the same source file or in other files. Like classes, class templates are declared by either defining the class' interface or declaring that the class is defined elsewhere. A primary class template is declared using the following form:
template <class TYPE_ID, data_type VALUE_ID > class list;
For a primary class template, it is illegal to follow the class' identifier with a set of template actual arguments (such as types or non-type values). For example, the following is illegal:
template <class TYPE_ID, data_type VALUE_ID >
class list <TYPE_ID, 10>;
11.3.3 Default Template Arguments
Default Template Arguments: Initial values specified in the class template's definition or declaration to be used when trailing actual arguments are not supplied by the client.
Class templates declared to have default template formal arguments can be used by the client with fewer actual arguments. Default arguments may be specified for type dependencies, non-type values, and template class arguments. For example, it is possible with default arguments for the client to create an instance of the class specifying zero, one, two, or all of the actual template arguments. The compiler supplies the missing arguments. They are automatically assigned the default values that are specified in the first occurrence of the template's declaration. A template formal argument may not be given different default arguments by more than one class template declaration in the same scope.
Default arguments can be specified in a class template's definition or declaration using the following form:.
template <class TYPE=int> class list;
When specifying default values, we add them to the class template's formal argument list working from the right to the left. For example, with a class template that has three formal arguments, we have four different choices: (1) Specify no default arguments which means clients must provide all three arguments; (2) Specify the last formal argument as having a default value, which means clients must either specify the first two arguments or all three arguments; (3) Specify the second to the last and the last formal arguments as having defaults, which means clients must specify the first, the first two, or all three arguments; or (4) Specify default arguments for all three formal arguments, which means clients can cause an instantiation of this class using no arguments at all. To create an instantiation of a class using all default values for initialization, empty angle brackets must be used by the client:
list <> object;
The following demonstrates a set of sample situations for a class template where there are three formal arguments:
(1) Specify no default arguments:
Class template
declaration:
template <class TYPE1, int sz, template<TYPE1> class
TYPE2> class list;
Client instantiates a
template class:
list <int, 100, stack> list_object;
(2) Specify one default argument (it must be the right most):
Class template
declaration:
template <class TYPE1, int sz, template<TYPE1> class
TYPE2 = stack>
class list;
Client instantiates a
template class:
list <int, 100, stack> list_object1;
list <int, 100> list_object2;
(3) Specify two default arguments (they must be the right two):
Class template
declaration:
template <class TYPE1, int sz = 100, template<TYPE1>
class TYPE2 =
stack> class list;
Client instantiates a
template class:
list <int, 100, stack> list_object1;
list <int, 100> list_object2;
list <int> list_object3;
(4) Specify all three as having default arguments:
Class template
declaration:
template <class TYPE1 = int, int sz = 100,
template<TYPE1>
class TYPE2 = stack> class list;
Client instantiates a
template class:
list <int, 100, stack> list_object1;
list <int, 100> list_object2;
list <int> list_object3;
list <> list_object3;
In the previous example, specifying the angle brackets in list <> list_object3 is necessary when using all default values. It would be incorrect to leave them out.
It is illegal to specify a default value for a template formal argument that appears in the list before other formal arguments that do not have defaults. Clients may omit only trailing arguments.
Practical Rule: If a template formal argument is given a default value, then all subsequent template formal arguments in the list must also have defaults supplied.
11.3.4 Member Functions of a Class Template
Member functions that are part of a class template are themselves template functions. Therefore, we must preface member functions defined outside of the class with the template's header. It is best to use the identical template header that precedes the class' definition. However, we are allowed to change the template formal argument names, just so the type and order is kept consistent.
Member functions can either be defined inside of a class template as inline members or separated from the class' definition. When this is done, we must use the following rules. First, in the implementation file we must first declare the template class being referenced within the file. Then, prior to each member function's definition, we must specify that they are part of the class template. This means that we must preface the member function with the identical template header that precedes the class definition. Then, the compiler must be informed that these functions are within the class scope. Therefore, the class name should preface the function name, followed by the scope resolution operator.
template <class TYPE1, int sz, template<TYPE1> class TYPE2>
list & list<TYPE1,sz,TYPE2>::operator = (const list &) {
//function's definition
}
The information supplied in the template's header specifies the class instantiation to which each function applies.
We should be careful if we change the names of our template's formal arguments in the member function implementations. If a name is selected that matches some other member, the member will hide the template's formal argument. The results may not be what we expect. To avoid this problem, we recommend keeping the class template's formal argument lists identical (i.e., using the same identifiers for each dependency) for every member function.
With non-template member functions that have the same name as a member function template, calling the function without angle brackets causes the non-template member function to be invoked. If explicit template arguments are specified in a function call instead (even if angle brackets are used with no arguments inside), then the template member function is invoked in its place.
11.3.5 Static Members of a Class Template
Template classes can support static data members. A static data member declared within a class template is considered to be itself a static data member template. Such static data members are declared within the class definition and are defined outside the class, possibly in the class implementation file. With template classes, the compiler automatically allocates memory for the static members for each instantiation of the class. Each template class instantiation has its own memory reserved for static data members. This means that for every template class that has a static data member, each of the instances of that class has it's own memory. Make sure to read this carefully! Each object of a particular template class instantiation shares the same static data members' memory. But, objects of different template class instantiations do not share the same static data members' memory.
The following illustrates the declaration, definition, and initialization of a static data member. Static data members that are not initialized upon definition are automatically initialized to their zero equivalent value.
template <class TYPE>
class stack {
static int data;
}
template <class TYPE>
int stack<TYPE>::data = 100;
Practical Notes: For each instantiation of a class, memory is allocated for all static data members. All objects of that instantiation will share that memory. Therefore, for every static data member there exists one instance per class instantiation.
11.3.6 Member Templates of a Class Template
Member Template : A template defined or declared within a class or a class template.
Like data members and member functions, templates that are declared within a class or class template are themselves members. Such templates are called member templates. Member templates cannot be defined in classes that have local scope.
Member templates may be either defined inside a class' interface or declared in a class and defined elsewhere. Member templates must be declared if they are to be used within the class (such as by member functions). A member template is declared using the following form:
template <class TYPE1, int sz, template<TYPE1> class TYPE2>
class list {
//a member template follows
template <class TYPE3>
int function(data_type arg);
};
Member templates that are defined outside of a class template must specify that they are part of the class template. This means that we must preface each member function with the identical template header that precedes the class definition. Then, the compiler must be informed that this template is within the class scope. Therefore, the class name should preface the function name, followed by the scope resolution operator.
template <class TYPE1, int sz, template<TYPE1> class TYPE2>
template <class TYPE3>
int list<TYPE1,sz,TYPE2>::function(data_type arg) {
//definition of the template function
...
}
11.4 Using Class Templates
Template Actual Arguments: Types and values used for a particular instantiation of a class template.
With parametric polymorphism, the client defining objects must specify the type(s) and values that are expected by the class template. This enables the compiler to generate the corresponding instantiations. Unlike template functions, where this can automatically be determined by the type of the function call's actual arguments, class templates require that the client defining an object of a particular class also specify the template actual arguments to be used for instantiation. Instantiation takes place at compile time. Therefore, the type and value of the template actual arguments must be known at compile time.
As we begin to use template classes as the types of our objects, we should realize that it is invalid to use a class in such a way that it requires a completely defined type prior to defining the class' interface. When classes are instantiated, they are placed in the same namespace where the class template is defined (i.e., not in the namespace where we use the class as a type).
The following sections introduce how a client can cause class templates to be implicitly and explicitly instantiated and how clients can specify type dependencies. This begins with a discussion of template class instantiation, and then continues with a discussion of rules clients must follow when specifying template type and non-type actual arguments.
11.4.1 Template Class Instantiation
When defining an object, clients must specify the data types and values used for substitution with the type and non-type dependencies. For example, if a class template has one type dependency and one non-type, the client could define objects of this class using the following form:
class_name <data_types, values> object;
With parametric polymorphism, the client must specify the type(s) and value(s) to be used for all type dependencies and non-type values. The compiler will cause an instantiation of that class to be created where all type dependencies are replaced by the actual arguments supplied. All non-type formal arguments are replaced by the values supplied.
Only instances used by the client cause code to be generated for any particular compilation; just like with template functions. Before generating an instance, the compiler first determines whether or not an instance matching the actual arguments specified by the client has been generated previously. If the identifiers representing the types expected match exactly and if the non-type template arguments have the identical values as a previous instantiation, the template class is not re-instantiated. For non-type arguments, the compiler uses the resulting value of the actual argument and not the expression used to generate that value. This means that 2*10, 20, and 30-10 would all correspond to the same non-type value. Standard conversions are applied to an expression used in the actual argument list to bring it to the type of the non-type formal argument.
When the non-type argument is a pointer to a function, the actual argument must exactly match the formal non-type argument. Local types cannot be used as the actual arguments for non-type formal arguments. We are also restricted from using the addresses of array elements and non static class members as actual non-type arguments.
The client can then define objects of various types and sizes by using different data types and different non-type values. The compiler will generate an instance of the corresponding class for each unique combination of actual arguments supplied by the client. For example, the following three object definitions will cause three different instantiations of class stack to be generated by the compiler:
stack <int, 30> stackone;
stack <int, 100> stacktwo;
stack <float, 150> stackthree;
In this example, the class designer should consider whether the benefits of having a non-type value supplied by the client outweigh the drawbacks of code proliferation. In this case, the non-type argument represents the size of the stack. Here, it is more advantageous to dynamically allocate memory at run time rather than cause separate instantiations of the code to be generated depending on the size of the stack desired.
Practical Rule: Template instantiation is the process of type substitution; it occurs when we define objects of that template class.
11.4.2 Implicit Instantiation
Unless the template class has been explicitly instantiated or explicitly specialized, it will be implicitly instantiated when it is referenced in a context that requires a completely defined type. And then, it is instantiated only once for any given set of template actual arguments.
Implicit instantiations take place when a class or static data member is implicitly generated by an implementation using the template definition. For example, class_name <int> object causes an implicit instantiation where int is substituted for TYPE (e.g., template <class TYPE> class class_name;). However, not every use of a template class causes the implementation to provide an implicit instantiation of that class. Only if the template is referenced in a context that requires the class to be defined will the template be implicitly instantiated. For example, objects are not formed when we say class_name <int> * ptr; therefore, the class does not need to be defined and an integer instantiation of this class is not generated. However, when this pointer is used in a way that requires the pointer type to be defined (such as saying *ptr), then an instantiation will be generated at that time.
If we define data members that are themselves template classes, the classes are not implicitly instantiated until those members are first used. Such data members might be first used by the client, a member function, or even a friend function. In the following class, the list_object data member's class is not implicitly instantiated until the point at which data member is first used:
template <class TYPE, int sz>
class stack {
public:
stack();
int push(const TYPE & data);
TYPE & pop();
private:
//this does not instantiated the list class
list <int, 100, stack> list_object;
};
template <class TYPE, int sz>
int stack<TYPE,sz>::push(const TYPE & data) {
//if this is the first usage of the list_object member,
//then the list class is instantiated
list_object <<data;
...
Template member functions are not instantiated if they are not needed. They are only instantiated if they are used by the client program. On the down side, unless explicit instantiation (explained in the next section) is requested, errors can only be found by explicitly using all member functions for each use expected. This applies to syntax errors as well as run time errors.
11.4.3 Explicit Instantiation
Clients can cause explicit instantiations to take place by placing the keyword template in front of a class name when defining objects. Explicit instantiations can be used to improve the performance of our compiles and links. It can be used to support better code generation and faster and more predictable compile and link times. The drawback is that a template class can only be explicitly instantiated once, for a particular set of template actual arguments.
By explicitly instantiating a class we also cause all of its member functions and static data members to also be explicitly instantiated unless they have previously been explicitly instantiated themselves. We can also explicitly instantiate just select member functions of a class template if all member functions are not needed. Explicit instantiations of a class are placed in the same namespace where the class template is defined, which is the same if the class were implicitly instantiated.
There are some restrictions when we explicitly instantiate a class. Default template arguments cannot be used in an explicit instantiation. And, the class template must be declared prior to explicitly instantiating such a class. For example, the following class declaration is required prior to explicitly instantiating a class, unless the class is already defined:
//class template declaration
template <class TYPE, int sz> class stack;
template class stack<int,100>; //explicit instantiation
Practical Rule: Explicit instantiation should be considered when implicit instantiation it too time consuming.
11.4.4 Template Type Actual Arguments
For every formal argument that represents a type dependency, the client must specify a corresponding data type. These data types may correspond to any fundamental, compound, or user defined data type and must be known at compile time. For user defined data types, the type specified must have external linkage. This means that the client cannot specify a local type. For example, if a user defined type is declared within a function, that type cannot be used as an actual argument to a template class.
11.4.5 Non-type Actual Arguments, Passed by Reference
For every formal argument that represents a non-type reference, the client must specify an lvalued expression with external linkage. Non-type actual arguments that are passed by reference must represent either an object or a function with external linkage. This means that the actual arguments may not be represented by temporaries. Non-type actual arguments may be globals, but they may not be static globals, locals, or even literals.
int i; //global
static int j //static global
class_name <i> object1; //legal
class_name <j> object2; //illegal
With non-type actual arguments, C++ uses standard type promotion and conversion rules to convert values implicitly to minimize the loss of information.
11.4.6 Non-type Actual Arguments, Passed by Value
For every formal argument that represents a non-type non-reference, the client must specify a corresponding value. Since the instantiations of a template class are determined at compile time, all non-type actual arguments passed by value must be represented by constant expressions. These expressions must involve only literals (excluding character strings) and constants (excluding floating point types or void types) and must be able to be evaluated during compilation rather than at run-time. C++ does not allow template arguments to consist of the address of an array element or an address of a non-static data member.
Be careful when specifying constant expressions that use comparison operators (in particular, the greater than operator). While C++ allows us to nest angle brackets, the first non-nested > bracket signifies the end of the template's actual argument list. Therefore, class_name<literal> another_literal> is illegal; it does not first compare the two literals and use the result as the value for instantiation.
With non-type actual arguments, C++ uses standard type promotion and conversion rules to convert values implicitly to minimize the loss of information.
11.4.7 Using the Name of a Template Class within Class Scope
Within a class template, using the name of the class is equivalent to using the name of the class qualified by the template's formal argument list. This allows constructors of class templates to be implemented in the same way that we learned:
template <formal arg list> class list {
list obj; //equivalent to list<formal arg list> obj;
list (const list &);
};
11.4.8 Using Operators with Class Templates
There are three operators that require special attention with class templates. When we use a class identifier as the second operand of the direct or indirect member access operators (the . and -> operators) or after the scope resolution operator, we must precede the class' identifier with the keyword template. For example, if node is a class template then it would need to be qualified as shown:
list_object->template node<int> * ptr = new node<int>;
In this example, it would be illegal to say the following. In this case, node is assumed to be a non-template member.
//the following is illegal if node is a class template
list_object->node<int> * ptr = new node<int>;
11.4.9 Binding
The compiler binds as many identifiers as possible when it reaches the template definition. Identifiers that depend on the template arguments are bound when the template is instantiated. Identifiers that do not depend on a template argument are bound at the template definition, not at the individual instantiations. We can force an identifier, not dependent on a template argument, to be bound when the template is instantiated by preceding it with the template argument and the scope resolution operator. For example, this delays the binding of name_to_be_resolved until the template is instantiated.
template <class TYPE1, int sz, template<TYPE1> class TYPE2>
class list {
typename TYPE1::name_to_be_resolved();
};
When the client uses an identifier that refers to a template class, followed by a < symbol, the compiler will always expect the template's actual arguments to follow. In this situation, the < symbol is never regarded as a less than operator.
11.5 Customizing Class Templates
Partial Specialization: An implementation of a class template or template member that is customized for a specified set of template actual arguments.
As we found with template functions, not every function can apply generically to all data types. Supporting special cases is particularly important when dealing with class templates. For some types, we may find that certain member functions need partial specialization. For other types, we may find that additional data members are required. To support special cases, we can implement specific instantiations of our member functions and classes to customize the functionality for a given set of data types and values. We call these instantiations partial specializations of our member functions and class templates. When all formal arguments are given customized types and/or values, we call this an explicit specialization.
The following sections demonstrate how we can create explicit specializations of member functions and class templates through a progression of examples.
11.5.1 Partial Specialization of Member Functions
Partial specialization of a member function allows us to implement a specific instance of that function. We specify the data type(s) and/or value(s) that apply to this particular member function. When the client specifies template actual arguments that match these data type(s) and/or value(s), the specialized member function is used.
Partial specialization is the process of creating a non-template member function. It allows us to implement a member function that has a different behavior from the general purpose member function supplied with the class template. This is primarily useful when a member function's operations must be customized to support certain types of data.
To specialize a member function, we must define the specialized version of the function. A partial specialization specifies what it expects as the template's actual arguments following the class identifier. These arguments must be specified in the order that corresponds to the formal template argument list. Not all arguments must be specialized; in these situations, the identifiers specified in the formal template argument list may be used instead of specific types and/or values. When all arguments are specialized, we have an explicit specialization; in this case, the template's formal argument list is empty (e.g., template <>). Default arguments cannot be used in conjunction with a partial specialization.
The order of the template versus the non-template version of a member function is not important. The compiler will always first select an exact match of the arguments with a non-template function before creating an instance of the class template member function or before performing promotions or standard conversions with the non-template function.
The following is an example specialization of a member function:
template <class TYPE>
class stack {
private:
TYPE * stack_array;
const int stack_size;
int stack_index;
public:
stack (int size=100): stack_size(size), stack_index(0) {
stack_array = new TYPE[size];
}
//Add other member functions, such as push...pop...
//etc.
}
//A template member function
template <class TYPE>
void stack<TYPE>::push(TYPE item) {
if (stack_index < stack_size) {
stack_array[stack_index] = item;
++stack_index;
}
}
//An explicit specialization of a member function, used
//when the client uses a pointer to a character as the
//stack type
template <>
void stack <char *>::push(char * item) {
if (stack_index < stack_size) {
stack_array[stack_index] = new char[strlen(item)+1];
strcpy(stack_array[stack_index], item);
++stack_index;
}
}
11.5.2 Partial Specialization of Class Templates
Partial specialization of a class template is the mechanism that we can use to cause one implementation of a class to have different behavior than another. This is primarily useful when a class requires different behavior depending on the data types used by the client. The reasons for doing this are the same as for creating a partially specialized template function or member function except that the specialization applies to the class as a whole.
To specialize a class template, we must define the specialized version of the class after the class template is defined or declared (called the primary template). Since the specialized version is a distinct definition, all members must be completely defined for that version. This means that all member functions must be defined for a specialized class template, even in the case where the functionality is unchanged. A specialized class template cannot rely on the primary template for any data or operations.
A partial specialization specifies what it expects as the actual arguments following the class identifier. These arguments must be specified in the order that corresponds to the formal template argument list. They cannot be identical to the implicit argument list of the primary template. For partial specializations we can still use some of the identifiers specified in the formal template argument list. This means that not all arguments must be specialized. If a non-type argument is not specialized, we use the identifier of the non-type formal argument (e.g., using sz in the third example matches any integer size specified by the client). When all arguments are specialized, we have an explicit specialization; in this case, the template's formal argument list is empty (e.g., template <>). Default arguments cannot be used in conjunction with a partial specialization. Specialized non-type arguments cannot be based on unspecified type identifiers. The following examples illustrate possible partial specializations:
//primary class template
template <class TYPE1, int sz, template<TYPE1> class TYPE2>
class list {...}
//illegal-same as primary
template <class TYPE1, int sz, template<TYPE1> class TYPE2>
class list<TYPE1, sz, TYPE2>{...}
//partial specialization
template <class TYPE1, int sz, template<TYPE1> class TYPE2>
class list <TYPE1 *,sz,TYPE2> {...}
//partial specialization
template <int sz, template<TYPE1> class TYPE2>
class list<list, sz,TYPE2<list>>{...}
//partial specialization
template <template<TYPE1> class TYPE2>
class list<list,100,TYPE2<list>>{...}
//explicit specialization
template <> class list<list,100,list<list>>{...}
The following example is a complete example of a class template's partial specialization:
template <class TYPE>
class stack {
private:
TYPE * stack_array;
const int stack_size;
int stack_index;
public:
stack (int size=100): stack_size(size), stack_index(0) {
stack_array = new TYPE[size];
}
void push(TYPE item);
TYPE pop(void);
}
template <class TYPE> class stack <char *> {
private:
char ** stack_array;
int stack_index;
public:
stack(int size=100): stack_index(0){
stack_array = new char *[size];
}
void push(const char * item) {
stack_array[stack_index] = new char[strlen(item)+1];
strcpy(stack_array[stack_index], item);
++stack_index;
}
};
The primary template must be declared prior to any specializations. Partially specialized templates must be declared prior to the client forming objects of that type (prior to an implicit instantiation).
A partial specialization class template will be used only if it is declared before the template is used in a situation that would cause an implicit instantiation to take place. If a declaration of a specialization is not within scope, the use of a template will cause the primary template to be instantiated instead.
The definition of a primary template's members are never used as the definition of a specialized template class; all members declared in a specialized template class must be completely defined. This includes all member functions!
11.5.3 Partial Specialization of Static Data Members
We can also create partial specializations of static data members. For example:
template <> int stack<int,0>::data = 0;
This specialized static data member will only be used if the first template argument is an integer and the second template argument is a zero. If 100 is used as the second argument, the primary static data member will be used instead! Therefore, a more common approach is to supply the size of our stack as an argument to the constructor instead of as an argument to the template's argument list.
When we have both a template class and a specialization of that class where there is a static data member, we must define the static data member for both the primary template class and for the specialization.
11.5.4 Binding of Partial Specializations
When the client uses a template class in a situation that requires the class to be instantiated (e.g., when defining an object of the class), the compiler must determine whether or not to generate an instantiation. If a partial specialization exists that matches the client supplied template actual arguments, then the instantiation is generated from the specialization. If the actual argument list does not match any of the supplied partial specializations, then the compiler will generate an instantiation of the primary template.
If more than one partial specialization matches the template's actual argument list, then the compiler will chose that instantiation which is the most specialized.
11.5.5 Using the Name of a Specialized Template Class within Class Scope
Within a partial specialization of a class template, using the name of the class is equivalent to using the name of the call qualified by the partial specialization:
template<class TYPE1> class list <TYPE1,100,list<list>>{
list obj; //equivalent to list<formal arg list> obj;
list (const list &);
};
11.6 Template Classes and Friends
Template classes can specify that other functions or classes be friends. Friends of a template class can exist by declaring each friend in the template class definition. Friends cannot be declared in local classes. The syntax is the same as we would expect for a non-template class.
If the friend specified is a non-template class or a non-template function, then the friend is actually a friend of each instantiation of the template class. If the friend specified is a template function or a template class, then each instantiation of the friend class or function is treated as a friend of each instantiation of the template class.
Alternatively, we can specify a friend function or class as being a bound template function or bound template class. A bound friend is a specialization of a template function or template function. This means that the specialization of some other template function or class can share information from all instantiations of the template class. We are granting friendship to a particular instantiation of a function or class; allowing only that version of the function or class to share the information in all instantiations of this class.
11.7 Using Separate Files
Member functions can be defined as inline in the same file as the class's interface using the inline keyword or defined in a separate file. When we define the member functions of our class templates in a separate implementation file, we define the class' interface in a header file in the same way that we would do for a non-template class. To do so requires that we define or declare our member function templates with the export keyword. This tells the compiler that the functions can be used in other "translation units" and may be compiled separately. In such cases the class only needs to be declared prior to defining objects of that class.
There are some restrictions. Templates defined in an unnamed namespace cannot be exported. But, an exported template only needs to be declared in the file in which it is instantiated. Declaring member functions of a class template as exported means that its members can be exported and used in other files.
The following example demonstrates how this can be done by specifying three separate files. One for the main set of functions, another that specifies the class template declarations, and a third where the member function templates and any specializations are implemented. Notice the use of the export keyword.
//main.cpp
//main set of functions
#include "t_class.h"
main() {
t_class<int, float> obj; //defining an object
}
//t_class.h
//declarations of the function template(s)
template<class TYPE_ID1, class TYPE_ID2>
class t_class {
public:
t_class();
t_class(const t_class &);
~t_class();
void t_member(TYPE_ID1, TYPE_ID2);
private:
TYPE_ID1 data_1;
TYPE_ID2* ptr_2;
};
//t_class.cpp
//implementation of the member function
export template<class TYPE_ID1, class TYPE_ID2>
void t_member(TYPE_ID1 arg_1, TYPE_ID2 arg_2) {...}
export template<class TYPE_ID1, class TYPE_ID2>
t_class() {...}
export template<class TYPE_ID1, class TYPE_ID2>
t_class(const t_class & source) {...}
export template<class TYPE_ID1, class TYPE_ID2>
~t_class() {...}
By specifying our member functions in separate compilation files, clients need only include the file that contains the declarations to define objects of that class and cause instantiations to be generated. If the keyword export was not used, the definition of the function must be within the scope of where we define the objects. This would require that we include the implementation file in our header file and ultimately in client code as well (e.g., t_class.h could include t_class.cpp in this case, where member functions are treated the same as inline functions).
So when should we use export and when should we simply include our function template implementations in our header files? When we have large classes or functions or when there are many type dependencies or specializations, it is often easier to debug our code by using separate compilation units and use the exporting process. On the other hand, if we have small classes and functions, few type dependencies, and limited (to no) specialized templates, then there may be no advantage to compiling them separately.
11.8 Caution!
One of the most serious drawbacks of class templates is the danger of generating an instance of the class that is incorrect because of type conflicts. Except by writing specialized template classes to take care of such cases, there is no way to protect the client from using the class incorrectly, causing erroneous instances of the class to be generated. Be very careful when writing class templates to ensure that all possible combinations will create correct classes.
Realize that when we write a class template we may not know the types that will be used by the client. Therefore, we recommend using type dependencies only once within the class's formal argument list. Also, make sure to handle the use of both built-in and pointer types.