16.1 Template Definitions
16.1.1 Defining a Function Template
A function template is a type-independant function that is used as a formula for generating a type-specific version of the function.
Template version of compare function:
// implement strcmp-like compare function
// return 0 if equal, 1 if v1 is bigger, -1 otherwise
template <typename T>
int compare (const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
A template definition starts with the keyword
template followed by a
template parameter list, which is a comma-separated list of one or more template parameter bracketed by the less-than (<) and greater-than (>) tokens.
The template parameter list cannot be empty.
Template Parameter List
The template parameter list acts much like a function parameter list. Template parameters represent types or values we can use in the definition of a class or function.
A template parameter can be a type parameter, which represents a type, or anontype parameter, which represents a constant expression(16.1.5 P 632).
A type parameter is defined following the keyword class or typename.
Using a Function Template
When we use a function template, the compiler infers what template arguments to bind to the template parameters. Once the compiler determines the actual template argument, itinstantiates an instance of the function template for us.
int main()
{
// T is int;
// compiler instantiates int compare (const int&, const int&)
cout << compare(1, 0) << endl;
// T is string;
// compiler instantiates int compare (const string&, const string&)
string s1 = "hi", s2 = "world";
cout << compare(s1, s2) << endl;
return 0;
}
inline Function Templates
A function template can be declared inline in the same way as a nontemplate function. the specifier is placed following the template parameter list and before the return type:
// ok: inline specifier follows template parameter list
template <typename T> inline T min(const T&, const T&);
// error: incorrect placement of inline specifier
inline template <typename T> T min(const T&, const T&);
16.1.2 Defining a Class Template
Defining our own Queue class template.
template <class Type> class Queue {
public:
Queue(); // default constructor
Type &front(); // return element from head of Queue
const Type &front() const;
void push(const Type &); //add element to back of Queue
void pop(); //remove element from head of Queue
bool empty() const; // true if no elements in the Queue
private:
// ....
};
Using a Class Template
In contrast to calling a function template, when we use a class template, we must explicitly specify arguments for the template parameters:
Queue<int> qi;
Queue<vector<double> > qc;
Queue<string> qs;
The compiler uses the arguments to instantiate a type-specific version of the class.
16.1.3 Template Parameters
When we wish to use the type or value that a template parameter represents, we use the same name as the corresponding template parameter.
Template Parameter Scope
The name of a template parameter can be used after it has been declared as a template parameter and until the end of the template declaration or definition.
A template parameter with the same name as an object, function, or type declared in global scope hides the global name:
typedef double T;
template <class T> T calc (const T &a, const T &b)
{
// tmp has the type of the template parameter T not the global typedef
}
Restriction on the Use of a Template Parameter Name
A name used as a template parameter may not be reused within the template:
template <class T> T calc (const T &a, const T &b)
{
typedef double T; //error: redeclares template paramter T
}
The restriction also means the name of a template parameter can be used only once within the same template parameter list:
// error: illegal reuse of template parameter name
template <class V, class V> V calc(const V&, const V&);
The name of a template parameter can be reused across different templates
Template Declarations
We can declare a template without defining it:
// declares compare but does not define it
template <class T> int compare (const T&, const T&);
The names of the template parameters need not be same across declarations and definitions of the same template:
// all three calc refer to the same function template
template <class T> T calc(const T&);
template <class U> U calc(const U&);
template <class Type> Type calc(const Type&) {...}
Each template type parameter must be preceded either by the keyword class or typename; each nontype parameter must be preceded by a type name:
// error: must precede U by either typename or class
template <typename T, U> T calc (const T&, const U&);
16.1.4 Template Type Parameters
A template type parameter can be used as a type specifier any where in the template, in exactly the same way as a built-in or class type specifier.
Distinction Between typename and class
In a function template parameter list, the keywords typename and class have the same meaning and can be used interchangeably.
The keyword typename was added to C++ as part of Standard C++, so older programs are more likely to use the keyword class exclusively.
Designating Types inside the Template Definition
In addition to defining data or function members, a class may define type members.
When we want to use such types inside a function template, we must tell the compiler that the name we are using refers to a type.
template <class Parm, class U>
Parm fcn (Parm * array, U value)
{
Parm::size_type * p; // if Parm::size_type is a type, then a declaration
// if Parm::size_type is an object, then multiplication
}
We know that size_type must be a member of the type bound to Parm, but we don't know whether size_type is the name of a type or a data member.
By default, the compiler assumes that such names name data members, not type.If we want the compiler to treat size_type as a type, then we must explicitly tell the compiler to do so:
template <class Parm, class U>
Parm fcn(Parm* array, U value)
{
typename Parm::size_type * P; // ok:declares p to be a pointer
}
We tell the compiler to treat a member as a type by prefixing uses of the member name with the keyword typename.
Of course, this declaration puts an obligation on the types used to instantiate fcn: Those types must have a member named size_type that is a type.
16.1.5 Nontype Template Parameters
Nontype parameters are replaced by values when the function is called. The type of that value is sepcified in the template parameterlist.
// initialize elements of an array to zero
template <class T, size_t N> void array_init(T (&parm) [N])
{
for (size_t i = 0; i != N; ++i) {
parm[i]=0;
}
}
A template nontype parameter is a constant value inside the template definition.
When array_init is called, the compiler figures out the value of the nontype parameter from the array argument:
int x[42];
double y[10];
array_init(x); // instantiates array_init (int (&) [42])
array_init(y); // inst5antiate array_init (double (&) [10])
Type Equivalence and Nontype Parameters
Expressions that evaluate to the same value are considered equivalent template arguments for a template nontype parameter:
int x[42];
const int sz = 40;
int y[sz + 2];
array_init(x); // instantiates array_init (int (&)[42])
array_init(y); // equivalent instantiation
16.1.6 Writing Generic Programs
The template code always makes some assumptions about the types that will be used. For example, thecomparetemplate assumes the type should support less-than operation.
The operations performed inside a function template constrains the types that can be used to instantiate the function. It is up to the programmer to guarantee that the types used as the function arguments actually support any operations that are used, and that those operations behave correctly in the context in which the template uses them.
Writing Type-Independent Code
When writing template code, it is useful to keep the number of requirements placed on the argument types as small as possible.
16.2 Instantiation
The process of generating a type-specific instance of a template is known as instantiation.
Instantiating a Class
When we write:
Queue<int> qi;
the compiler automatically creates a class named Queue<int>.
Each instantiation of a class template constitutes an independent class type. The Queue instantiation for the type int has no relationship to nor any special access to the members of any other Queue type.
Class Template Arguments Are Required
A class template does not define a type; only a specific instantiation defines a type.
For example, Queue is not a type, Queue<int> or Queue<string> are.
Function-Template Instantiation
When we use a function template, the compiler will usually infer the template arguments for us:
int main()
{
compare(1,0); // ok:binds template parameter to int
compare(3.14, 2.7) // ok: binds template parameter to double
}
This program will instantiates two versions of compare.
16.2.1 Template Argument Deduction
The process of determing the types and values of the template arguments from the type of the function arguments is calledtemplate argument deduction.
Multiple Type Parameter Arguments Must Match Exactly
A template type parameter may be used as the type of more than one function parameter. In such case, the deduced types must match:
template <typename T>
int compare (const T& v1, const T& v2) {...}
int main()
{
short si;
// error: cannot instantiate compare(short, int)
compare (si, 1024);
return 0;
}
If the designer of compare wants to allow normal conversions on the arguments, then the function must be defined with two type parameters:
// argument types can differ, but must be compatible
template <typename A, typename B>
int compare(const A& v1, const B& v2) {...}
A < operator must exist that can compare values of those types.
Limited Conversions on Type Parameter Arguments
In general, arguments are not converted to match an existing instantiation (egP 638); in stead, a new instance is generated.
There are only two kinds of conversions that the compiler will perform rather than generating a new instantiation(P 639):
1. const conversion.
2. array or function to pointer conversion.
E.G:
template <typename T> T fobj (T,T);
template <typename T> T fref (const T&, const T&);
string s1("a value");
const string s2("another value");
fobj(s1, s2); // ok:calls f(string, string), const is ignored
fref(s1, s2); // ok: nonconst object s1 converted to const reference
int a[10], b[42];
fobj(a,b); // ok: calls f(int *, int*)
fref(a,b); // error: array types don't match; arguments aren't converted to pointers
Normal Conversion Apply for Nontemplate Arguments
The restriction on type conversion applies only to those arguments whose types are template parameters.
template <class Type> Type sum (const Type &op1, int op2) { ... }
Because the type of op2 is fixed, we can pass int, double... as the second argument.
Template Argument Deduction and Function Pointers