The C++ template facility goes far
beyond simple “containers of T.” Although the original motivation was to
enable type-safe, generic containers, in modern C++, templates are also used to
generate custom code and to optimize program execution through compile-time
programming constructs. Comment
In this chapter we offer a practical look at the power (and
pitfalls) of programming with templates in modern C++. For a more complete
analysis of template-related language issues and “gotchas,” we recommend the
superb book by David Vandevoorde and Nico Josuttis.ÂŽÂŽ Comment
As we illustrated in Volume 1, templates come in two
flavors: function templates and class templates. Both are wholly characterized
by their parameters. Each template parameter itself can represent one of the
following categories of arguments:
1. Types
(either built-in or user-defined)
2. Compile-time
constant values (for example, integers, and pointers and references to static
entities; often referred to as non-type parameters)
3. Other
templates
The examples in Volume 1 all fall into the first category
and are the most common. The canonical example for simple container-like
templates nowadays seems to be a Stack class. Being a container, a Stack
object is not concerned with the type of object it stores; the logic of holding
objects is independent of the type of objects being held. For this reason you
can use a type parameter to represent the contained type: Comment
template<class T>
class Stack {
ÂŽÂŽ T* data;
ÂŽÂŽ size_t count;
public:
ÂŽÂŽ void push(const T& t);
ÂŽÂŽ // etc.
};
You provide the actual type to be used for a particular Stack
instance by means of an argument for the parameter T:
Stack<int>
myStack;ÂŽÂŽ // A Stack of ints
The compiler then provides an int-version of Stack
by substituting int for T and generating the corresponding code.
The name of the class instance generated from the template in this case is Stack<int>.
Comment
It is also possible to provide a non-type template
parameter, as long as it represents an integral value that is known at compile
time. You can make a fixed-size Stack, for instance, by specifying a
non-type parameter to be used as the dimension for the underlying array, as
follows. Comment
template<class T, size_t
N>
class Stack {
T data[N]; // Fixed capacity
is N
size_t count;
public:
void push(const T& t);
// etc.
};
You must provide a compile-time constant value for the
parameter N when you request an instance of this template, such as
Stack<int, 100>
myFixedStack;
Because the value of N is known at compile time, the
underlying array (data) can be placed on the runtime stack instead of on
the free store, which can improve runtime performance by avoiding the overhead
associated with dynamic memory allocation. Following the pattern mentioned
earlier, the name of the class above is Stack<int, 100>. This
means that each distinct value of N results in a unique class type. For
example, Stack<int, 99> is a distinct class from Stack<int,
100>. Comment
The bitset class template, discussed in detail in Chapter
7, is the only class in the standard C++ library that uses a non-type template
parameter, which happens to specify the number of bits the bitset object
can hold. The following random number generator example uses a bitset to
track numbers so all the numbers in its range are returned in random order
without repetition before starting over. This example also overloads operator( )
to produce a familiar function-call syntax. Comment
//: C05:Urand.h
//{-bor}
// Unique randomizer
#ifndef URAND_H
#define URAND_H
#include <bitset>
#include <cstddef>
#include <cstdlib>
#include <ctime>
using std::size_t;
using std::bitset;
template<size_t UpperBound>
class Urand {
bitset<UpperBound> used;
public:
Urand() {
srand(time(0)); //
randomize
}
size_t operator()(); // The
"generator" function
};
template<size_t UpperBound>
inline size_t Urand<UpperBound>::operator()()
{
if(used.count() == UpperBound)
used.reset(); // start over
(clear bitset)
size_t newval;
while(used[newval = rand() % UpperBound])
; // Until unique value is
found
used[newval] = true;
return newval;
}
#endif // URAND_H ///:~
The uniqueness of Urand is produced by tracking with
a bitset all the numbers possible in the random space (the upper bound
is set with the template argument) and by recording each number as itÂ’s used by
setting the corresponding position bit in used. When the numbers are all
used up, the bitset is cleared to start over. HereÂ’s a simple driver
that illustrates how to use a Urand object: Comment
//: C05:UrandTest.cpp
//{-bor}
#include <iostream>
#include "Urand.h"
using namespace std;
int main() {
Urand<10> u;
for(int i = 0; i < 20; ++i)
cout << u() << '
';
} ///:~
As we explain later in this chapter, non-type template
arguments are also important in the optimization of numeric computations. Comment
You can provide default arguments for template parameters in
class templates. (They are not allowed in function templates.) As with default
function arguments, they should only be defined once, the first time a template
declaration or definition is seen by the compiler; and once you introduce a
default argument, all the subsequent template parameters must also have
defaults. To make the fixed-size Stack template shown earlier a little
friendlier, for example, you can add a default argument like this: Comment
template<class T, size_t N =
100>
class Stack {
T data[N]; // Fixed capacity
is N
size_t count;
public:
void push(const T& t);
// etc.
};
Now, if you omit the second template argument when declaring
a Stack object, the value for N will default to 100. Comment
You can choose to provide defaults for all arguments, but
you must use an empty set of brackets when declaring an instance so that the
compiler knows that a class template is involved. HereÂ’s how:
template<class T = int,
size_t N = 100> // Both defaulted
class Stack {
T data[N]; // Fixed capacity
is N
size_t count;
public:
void push(const T& t);
// etc.
};
Stack<>
myStack; // Same as Stack<int, 100> Comment
Default arguments are used heavily in the standard C++
library. The vector class template, for instance, is declared as
follows:
template <class T, class
Allocator = allocator<T> >
class vector;
Note the space between the last two right angle bracket
characters. This prevents the compiler from interpreting those two characters (>>)
as the right-shift operator.
This declaration reveals that vector actually takes
two arguments: the type of the contained objects it holds, and a type that
represents the allocator used by the vector. (We talk more about allocators in
Chapter 7.) Whenever you omit the second argument, the standard allocator
template is used, parameterized by the first template parameter. This
declaration also shows that you can use template parameters in other,
subsequent template parameters, as T is used here. Comment
Although you cannot use default template arguments in
function templates, you can use template parameters as default arguments to
normal functions. The following function template adds the elements in a
sequence. Comment
//: C05:FuncDef.cpp
#include <iostream>
using namespace std;
template<class T>
T sum(T* b, T* e, T init = T())
{
while(b != e)
init += *b++;
return init;
}
int main() {
int a[] = {1,2,3};
cout << sum(a, a+sizeof
a / sizeof a[0]) << endl; // 6
} ///:~
The third argument to sum( ) is the initial
value for the accumulation of the elements. Since we omitted it, this argument
defaults to T( ), which in the case of int and other
built-in types invokes a pseudo-constructor that performs zero-initialization. Comment
The third type of parameter a template can accept is another
class template. This may sound strange, since templates are types, and type
parameters are already allowed, but if you are going to use a template type
parameter as a template in your code, the compiler needs to know that the
parameter is a template in the first place. The following example illustrates a
template template parameter. Comment
//: C05:TempTemp.cpp
// Illustrates a template
template parameter
#include <cstddef>
#include <iostream>
using namespace std;
template<class T>
class Array { // A simple,
expandable sequence
enum {INIT = 10};
T *data;
size_t capacity;
size_t count;
public:
Array() {
count = 0;
data = new T[capacity =
INIT];
}
void push_back(const T& t)
{
if(count == capacity) {
// Grow underlying array
size_t newCap =
2*capacity;
T* newData = new
T[newCap];
for (size_t i = 0; i <
count; ++i)
newData[i]
= data[i];
delete data;
data
= newData;
capacity = newCap;
}
data[count++] = t;
}
void pop_back() {
if(count > 0)
--count;
}
T* begin() {
return data;
}
T* end() {
return data + count;
}
};
template<class
T, template<class> class Seq>
class
Container {
Seq<T> seq;
public:
void append(const T& t) {
seq.push_back(t);
}
T* begin() {
return seq.begin();
}
T* end() {
return seq.end();
}
};
int main() {
Container<int, Array>
theData;
theData.append(1);
theData.append(2);
int* p = theData.begin();
while(p != theData.end())
cout << *p++ <<
endl;
} ///:~
The Array class template is a trivial sequence
container. The Container template takes two parameters: the type of the
objects it is to hold, and a sequence data structure to do the holding. The
following line in the implementation of the Container class requires
that we inform the compiler that Seq is a template: Comment
If we hadnÂ’t declared Seq to be a template template
parameter, the compiler would complain here that Seq is not a template,
since weÂ’re using it as such. In main( ) a Container is
instantiated to use an Array to hold integers, so Seq stands for Array
in this example. Comment
Note that it is not necessary in this case to name the
parameter for Seq inside ContainerÂ’s declaration. The line in
question is:
template<class T,
template<class> class Seq>
Although we could have written
template<class T,
template<class U> class Seq>
the parameter U is not needed anywhere. All that
matters is that Seq is a class template that takes a single type
parameter. This is analogous to omitting the names of function parameters when
theyÂ’re not needed, such as when you overload the post-increment operator: Comment
The int here is merely a placeholder and therefore
needs no name.
The following program uses a fixed-size array, which has an
extra template parameter representing the array dimension:
//:
C05:TempTemp2.cpp
// A multi-variate
template template parameter
#include <cstddef>
#include <iostream>
using namespace std;
template<class T, size_t
N>
class Array {
T data[N];
size_t count;
public:
Array() { count = 0; }
void push_back(const T& t)
{
if(count < N)
data[count++] = t;
}
void pop_back() {
if(count > 0)
--count;
}
T* begin() { return data; }
T* end() { return data +
count; }
};
template<class T,size_t
N,template<class,size_t> class Seq>
class Container {
Seq<T,N> seq;
public:
void append(const T& t) {
seq.push_back(t); }
T* begin() { return
seq.begin(); }
T* end() { return seq.end(); }
};
int main() {
const size_t N = 10;
Container<int, N, Array>
theData;
theData.append(1);
theData.append(2);
int* p = theData.begin();
while(p != theData.end())
cout << *p++ <<
endl;
} ///:~
Once again, parameter names are not needed in the
declaration of Seq inside ContainerÂ’s declaration, but we need
two parameters to declare the data member seq, hence the appearance of
the non-type parameter N at the top level. Comment
Combining default arguments with template template
parameters is slightly more problematic. The When the compiler looks at the
inner parameters of a template template parameter, default arguments are not
considered, so you have to repeat the defaults in order to get an exact match. The
following example uses a default argument for the fixed-size Array
template and shows how to accommodate this quirk in the language. Comment
//: C05:TempTemp3.cpp
//{-bor}
//{-msc}
// Combining template template
parameters and
// default arguments
#include <cstddef>
#include <iostream>
using namespace std;
template<class T, size_t N =
10> // A default argument
class Array {
T data[N];
size_t count;
public:
Array() { count = 0; }
void push_back(const T& t)
{
if(count < N)
data[count++] = t;
}
void pop_back() {
if(count > 0)
--count;
}
T* begin() { return data; }
T* end() { return data + count;
}
};
template<class T,
template<class, size_t = 10> class Seq>
class Container {
Seq<T> seq; // Default
used
public:
void append(const T& t) {
seq.push_back(t); }
T* begin() { return
seq.begin(); }
T* end() { return seq.end(); }
};
int main() {
Container<int, Array>
theData;
theData.append(1);
theData.append(2);
int* p = theData.begin();
while(p != theData.end())
cout << *p++ <<
endl;
} ///:~
It is necessary to include the default dimension of 10 in
the line:
template<class T,
template<class, size_t = 10> class Seq>
Both the definition of seq in Container and theData
in main( ) use the default. The only way to use something other
than the default value is as the previous program (TempTemp2.cpp)
illustrated. This is the only exception to the rule stated earlier that default
arguments should appear only once in a compilation unit. Comment
Since the standard sequence containers (vector, list,
and deque, discussed in depth in Chapter 7) have a default allocator
argument, the technique shown above is helpful should you ever want to pass one
of these sequences as a template parameter. The following program passes a vector
and then a list to two instances of Container. Comment
//: C05:TempTemp4.cpp
//{-bor}
//{-msc}
// Passes standard sequences as
template arguments
#include <iostream>
#include <list>
#include <memory> // Declares
allocator<T>
#include <vector>
using namespace std;
template<class
T, template<class U, class = allocator<U> >
class Seq>
class Container {
Seq<T> seq; // Default
of allocator<T> applied implicitly
public:
void push_back(const T& t)
{ seq.push_back(t); }
typename
Seq<T>::iterator begin() { return seq.begin(); }
typename
Seq<T>::iterator end() { return seq.end(); }
};
int main() {
// Use a vector
Container<int, vector>
theData;
theData.push_back(1);
theData.push_back(2);
for(vector<int>::iterator p = theData.begin();
p
!= theData.end(); ++p) {
cout
<< *p << endl;
}
// Use a list
Container<int, list>
theOtherData;
theOtherData.push_back(3);
theOtherData.push_back(4);
for(list<int>::iterator
p2 = theOtherData.begin();
p2 != theOtherData.end();
++p2) {
cout << *p2 <<
endl;
}
} ///:~
In this case we name the first parameter of the inner
template Seq (with the name U), because the allocators in the
standard sequences must themselves be parameterized with the same type as the
contained objects in the sequence. Also, since the default allocator
parameter is known, we can omit it in the subsequent references to Seq<T>,
as we did in the previous program. To fully explain this example, however, we
have to discuss the semantics of the typename keyword. Comment
Consider the following: Comment
//: C05:TypenamedID.cpp
//{-bor}
// Uses 'typename' as a prefix
for nested types
template<class T> class X
{
// Without typename, you
should get an error:
typename T::id i;
public:
void f() { i.g(); }
};
class Y {
public:
class id {
public:
void g() {}
};
};
int main() {
X<Y> xy;
xy.f();
} ///:~
The template definition assumes that the class T that
you hand it must have a nested identifier of some kind called id. But id
could also be a static data member of T, in which case you can perform
operations on id directly, but you can’t “create an object” of “the type
id.” Comment
However, thatÂ’s exactly what is happening here: the
identifier id is being treated as if it were actually a nested type
inside T. In the case of class Y, id is in fact a nested
type, but (without the typename keyword) the compiler canÂ’t know that
when itÂ’s compiling X. Comment
If the compiler has the option of treating an identifier as a
type or as something other than a type when it sees an identifier in a template,
it will assume that the identifier refers to something other than a type. That
is, it will assume that the identifier refers to an object (including variables
of primitive types), an enumeration, or something similar. However, it will
not–cannot–just assume that it is a type. Comment
Because the default
behavior of the compiler is to assume that a name that fits the above two
points is not a type, you must use typename for nested names (except in
constructor initializer lists, where it is neither needed nor allowed). In the
above example, when the compiler sees template T::id, it knows (because
of the typename keyword) that id refers to a nested type and thus
it can create an object of that type. Comment
The short version of
the rule is: if a type referred to inside template code is qualified by a
template type parameter, it should be preceded by the typename keyword, unless
it appears in a base class specification or initializer list in the same scope
(in which case you must not).
All the above explains
the use of the typename keyword in the program TempTemp4.cpp.
Without it, the compiler would assume that the expression Seq<T>::iterator
is not a type, but we were using it to define the return type of the begin( )
and end( ) member functions. Comment
The following example,
which defines a function template that can print any standard C++ sequence,
shows a similar use of typename.
Once again, without
the typename keyword the compiler will interpret iterator as a
static data member of Seq<T>, which is a syntax error, since a
type is required. Comment
ItÂ’s important not to
assume that the typename keyword creates a new type name. It doesnÂ’t.
Its purpose is to inform the compiler that the qualified identifier is to be
interpreted as a type. A line that reads: Comment
causes a variable
named It to be declared of type Seq<T>::iterator. If you
mean to create a new type name, you should use typedef, as usual, as in:
Comment
Another role of the typename
keyword is to provide you the option of using typename instead of class
in the template argument list of a template definition. To some, this produces
clearer code (your mileage may vary): Comment
You probably wonÂ’t see
a great deal of code that uses typename in this fashion, since the
keyword was added to the language a relatively long time after templates were
introduced. Comment
Just as the typename
keyword helps the compiler in situations in which a type identifier is not
expected, there is also a potential difficulty with tokens that are not
identifiers, such as the < and > characters; sometimes they
represent the less-than or greater-than symbols, and sometimes they delimit
template parameter lists. As an example, weÂ’ll once more use the bitset
class: Comment
The bitset
class supports conversion to string object via its to_string member
function. To support multiple string classes, to_string is itself a
template, following the pattern established by the basic_string template
discussed in Chapter 3. The declaration of to_string inside of bitset
looks like this: Comment
Our bitsetToString( )
function template above allows you to request different types of string
representations of a bitset. To get a wide string, for instance, you
change the call to the following:
Note that basic_string
uses default template arguments, so we donÂ’t have to repeat the char_traits
and allocator arguments in the return value. Unfortunately, bitset::to_string
does not use default arguments. Using bitsetToString<char>( bs)
is more convenient than typing a fully-qualified call to bs.template
to_string<char, char_traits, allocator<char> >( ) every
time. Comment
The return statement
in bitsetToString( ) contains the template keyword in an odd
place—right after the dot operator applied to the bitset object bs.
This is because when the template is parsed, the < character after
the to_string token would be interpreted as a less-than operation
instead of the beginning or a template argument list. We explain exactly why
this confusion exists in the section “Name lookup issues” later in this
chapter. The template keyword used in this context tells the compiler
that what follows is the name of a template, causing the < character
to be interpreted correctly. The same reasoning applies to the -> and
:: operators when applied to templates. As with the typename
keyword, this template disambiguation technique can only be used within a
template. Comment
The bitset::to_string( )
function template is an example of a member template: a template
declared within another class or class template. This allows many combinations
of independent template arguments to be combined. A useful example is found in
the complex class template in the standard C+ library. The complex
template has a type parameter meant to represent an underlying floating-point
type to hold the real and imaginary parts of a complex number. The following
code snippet from the standard library shows a member-template constructor in
the complex class template: Comment
The standard complex
template comes ready-made with specializations that use float, double,
and long double for the parameter T. The member-template
constructor above allows you to create a new complex number that uses a
different floating-point type as its base type, as seen in the code below: Comment
In the declaration of w,
the complex template parameter T is double and X is
float. Member templates make this kind of flexible conversion easy. Comment
Since defining a
template within a template is a nesting operation, the prefixes that introduce
the templates must reflect that nesting if you define the member template
outside the outer class definition. For example, if you were to implement the complex
class template, and if you were to define the member-template constructor above
outside the complex template class definition, you would have to do it
like this: Comment
Another use of member
function templates in the standard library is in the initialization of containers,
such as a vector. Suppose we have a vector of ints and we
want to initialize a new vector of doubles with it, like this: Comment
As long as the
elements in v1 are assignment-compatible with the elements in v2
(as double and int are here), all is well. The vector class
template has the following member template constructor: Comment
This constructor is
actually used twice in the vector declarations above. When v1 is
initialized from the array of ints, the type InputIterator is int*.
When v2 is initialized from v1, an instance of the member template
constructor is used with InputIterator representing vector<int>::iterator.
Comment
Member templates can
also be classes. (They donÂ’t have to be functions, although thatÂ’s usually what
you need.) The following example shows a member class template inside an outer
class template. Comment
The typeid
operator, which is covered in Chapter 8, returns an object whose name( )
member function yields a string representation of a type or of the type of a
variable. Although the exact representation varies from compiler to compiler,
the output of the program above should be something like this: Comment
The declaration of the
variable inner in the main program instantiates both Inner<bool>
and Outer<int>. Comment
Member template
functions cannot be declared virtual. Current compiler technology
expects to be able to fix the size of a classÂ’s virtual function table when the
class is parsed. Allowing virtual member template functions would require
knowing all calls to such member functions everywhere in the program ahead of
time, which is not feasible, especially for multi-file projects. Comment
Just as a class template describes a family of classes,
a function template describes a family of functions. The syntax for creating
either type of template is virtually identical, but they differ somewhat in how
they are used. You must always use angle brackets when instantiating class
templates and you must supply all non-default template arguments. With function
templates, on the other hand, you can often omit the template arguments, and
default template arguments are not even allowed. Consider a typical
implementation of the min( ) function template declared in the <algorithm>
header, which looks something like this: Comment
You could invoke this
template by providing the type of the arguments in angle brackets, just like
you do with class templates, as in:
This syntax tells the
compiler that a specialization of the min template is needed with int
used in place of the parameter T, whereupon the compiler generates the
corresponding code. Following the pattern of naming the classes generated from
class templates, you can think of the name of the instantiated function as min<int>.
Comment
You can always use
such explicit function template specification as in the example above, but it
is often convenient to leave off the template arguments and let the compiler
deduce them from the function arguments, like this: Comment
If both i and j
are ints, the compiler knows that you need min<int>, which
it then instantiates automatically. The types must be identical, because the
template was originally specified with only one template type argument used for
both function parameters. No standard conversions are applied for function
arguments whose type is specified by a template parameter. For example, if you
wanted to find the minimum of an int and a double, the following
attempt at a call to min would fail: Comment
Since x and j
are distinct types, no single parameter matches the template parameter T
in the definition of min; so the call does not match the template
declaration. You can work around this difficulty by casting one argument to the
otherÂ’s type or by reverting to the fully-specified call syntax, as in: Comment
This tells the
compiler to generate the double version of min, after which j
can be promoted to a double by normal standard conversion rules (because
the function min<double>(const double&, const double&)
would then exist). Comment
You might be tempted
to require two parameters for min, allowing the types of the arguments
to be independent, like this:
This is often a good
strategy, but in this case it is problematic because min must return a
value, and there is no satisfactory way to determine which type it should be (T
or U?). Comment
If the return type of
a function template is an independent template parameter, you must always
specify its type explicitly when you call it, since there is no argument from
which to deduce it. Such is the case with the fromString template below.
Comment
These function
templates provide conversions to and from std:: string for any types
that provide a stream inserter or extractor, respectively. HereÂ’s a test
program that includes the use of the standard library complex number
type: Comment
The output is what
youÂ’d expect: Comment
Notice that in each of
the instantiations of fromString, the template parameter is specified in
the call. If you have a function template with template parameters for the
parameter types as well as the return types, it is important to declare the
return type parameter first; otherwise you wonÂ’t be able to omit the type
parameters for the function parameters. As an illustration, consider the
following well-known function template: Comment
If you interchange R
and P in the template parameter list near the top of the file, it will
be impossible to compile this program because the return type will remain
unspecified (since the first template parameter would be the functionÂ’s
parameter type). The last line (which is commented out) is illegal because
there is no standard conversion from int to char*; implicit_cast
is for revealing in your code conversions that are allowed naturally. Comment
With a little care you
can even deduce array dimensions. The following example has an array-initialization
function template (init2) that does just that.
Array dimensions are
not passed as part of a function parameterÂ’s type unless that parameter is
passed by pointer or reference. The function template init2 declares a
to be a reference to a two-dimensional array, so its dimensions R and C
are deduced by the template facility, making init2 a handy way to
initialize a two-dimensional array of any size. The template init1 does
not pass the array by reference, so the sizes must be explicitly specified,
although the type parameter can still deduced. Comment
As with functions, you
can overload function templates that have the same name. When the compiler
processes a function call in a program, it has to decide which template or
ordinary function is the “best” fit for the call. Assuming the existence of the
min function template introduced earlier, letÂ’s add some ordinary
functions to the mix: Comment
//: C05:MinTest.cpp
#include
<cstring>
#include
<iostream>
using std::strcmp;
using std::cout;
using std::endl;
template<typename
T> const T& min(const T& a, const T& b) {
return (a < b) ?
a : b;
}
const char* min(const
char* a, const char* b) {
return (strcmp(a, b)
< 0) ? a : b;
}
double min(double x,
double y) {
return (x < y) ?
x : y;
}
int main() {
const char *s2 =
"say \"Ni-!\"", *s1 = "knights who";
cout << min(1,
2) << endl; // 1: 1 (template)
cout <<
min(1.0, 2.0) << endl; // 2: 1 (double)
cout << min(1,
2.0) << endl; // 3: 1 (double)
cout <<
min(s1, s2) << endl; // 4: knights who (const
// char*)
cout <<
min<>(s1, s2) << endl; // 5: say "Ni-!"
// (template)
} ///:~
In addition to the
function template, this program defines two non-template functions: a C-style
string version of min and a double version. If the template doesnÂ’t
exist at all, the call in line 1 above have invokes the double version
of min because of the standard conversion from int to double.
Since the template can generate an int version, however, that is
considered a better match (of course!); so thatÂ’s what happens. The call in
line 2 is an exact match for the double version, of course, and the call
in line 3 also invokes the same function, implicitly converting 1 to 1.0. In
line 4 the const char* version of min is called directly. In line
5 we force the compiler to use the template facility by appending empty angle
brackets to the function name, whereupon it generates a const char*
version from the template and uses it (which is verified by the wrong
answer—it’s just comparing addresses!). If you’re wondering why we used using
declarations in lieu of the using namespace std; directive, some
compilers include headers behind the scenes that bring in std::min,
which would conflict with our declarations of the name min. Comment
As stated above, you
can overload templates of the same name, as long as they can be distinguished
by the compiler. You could, for example, declare a min function template
that processes three arguments:
Versions of this
template will be generated only for calls to min( ) that have three
arguments of the same type. Comment
In a number of
situations you need to take the address of a function. For example, you may
have a function that takes an argument of a pointer to another function. Of
course, itÂ’s possible that this other function might be generated from a
template function, so you need some way to take that kind of address: Comment
This example
demonstrates a number of issues. First, even though youÂ’re using templates, the
signatures must match. The function h( ) takes a pointer to a
function that takes an int* and returns void, and thatÂ’s what the
template f produces. Second, the function that wants the function
pointer as an argument can itself be a template, as in the case of the template
g. Comment
In main( )
you can see that type deduction works here too. The first call to h( )
explicitly gives the template argument for f, but since h( )
says that it will only take the address of a function that takes an int*,
that part can be deduced by the compiler. With g( ) the situation
is even more interesting because two templates are involved. The compiler
cannot deduce the type with nothing to go on, but if either f or g
is given int, the rest can be deduced. Comment
An obscure issue
arises when trying to pass the functions tolower or toupper,
declared in <cctype>, as parameters. It is possible to use these
in conjunction with the transform algorithm (which is covered in detail
in the next chapter), for example, to convert a string to lower or upper case.
Care must be taken, however, because there are multiple declarations for these
functions. A na ïve approach would is something like this: Comment
The transform
algorithm applies its fourth parameter (tolower in this case) to each
character in the string s and places the result in s itself, thus
overwriting each character in s with its lower-case equivalent. As it is
written, this statement may or may not work! It fails in the following context:
Comment
Even if your compiler
letÂ’s you get away with this, it is illegal. The reason is that the <iostream>
header also makes available a two-argument version of tolower and toupper:
These function
templates take a second argument of type locale. The compiler has no way
of knowing whether it should use the one-argument version of tolower
defined in <cctype> or the one mentioned above. You can solve this
problem (almost!) with a cast in the call to transform, as follows: Comment
(Recall that tolower
and toupper traffic in int instead of char.) The cast above
makes clear that the single-argument version of tolower is desired. Once
again, this works with some compilers, but it is not required to. The reason,
albeit obscure, is that a library implementation is allowed to give “C linkage”
(meaning that the function name does not contain all the auxiliary information that normal C++ functions do) to functions inherited
from the C language. If this is the case, the cast fails, because transform
is a C++ function template and expects its fourth argument to have C++
linkage—and a cast is not allowed to change the linkage. What a predicament! Comment
The solution is to
place calls to tolower in an unambiguous context. For example, you could
write a function, letÂ’s call it strTolower( ), and place it in its
own file without including <iostream>, like this: Comment
The header <iostream>
is not involved here, and the compilers we use do not introduce the
two-argument version of tolower in this context, so thereÂ’s no problem. You can then use this function
normally: Comment
Another solution is to
write a wrapper function template that calls the correct version of tolower
explicitly:
This version has the
advantage that it can process both wide and narrow strings since the underlying
character type is a template parameter. The C++ standards committee is working
on modifying the language so that the first example (without the cast) will
work, and some day these workarounds can be ignored. Comment
Suppose you want to take an STL sequence container
(which youÂ’ll learn more about in subsequent chapters; for now we can just use
the familiar vector) and apply a function to all the objects it
contains. Because a vector can contain any type of object, you need a function
that works with any type of vector: Comment
//:
C05:ApplySequence.h
// Apply a function to
an STL sequence container
// 0 arguments, any
type of return value:
template<class Seq,
class T, class R>
void apply(Seq&
sq, R (T::*f)()) {
typename
Seq::iterator it = sq.begin();
while(it !=
sq.end()) {
((*it)->*f)();
it++;
}
}
// 1 argument, any
type of return value:
template<class Seq,
class T, class R, class A>
void apply(Seq&
sq, R(T::*f)(A), A a) {
typename
Seq::iterator it = sq.begin();
while(it !=
sq.end()) {
((*it)->*f)(a);
it++;
}
}
// 2 arguments, any
type of return value:
template<class Seq,
class T, class R,
class A1,
class A2>
void apply(Seq&
sq, R(T::*f)(A1, A2),
A1 a1, A2 a2) {
typename
Seq::iterator it = sq.begin();
while(it !=
sq.end()) {
((*it)->*f)(a1,
a2);
it++;
}
}
// Etc., to handle
maximum likely arguments ///:~
The apply( ) function
template takes a reference to the container class and a pointer-to-member for a
member function of the objects contained in the class. It uses an iterator to
move through the Stack and apply the function to every object. Comment
Notice that there are
no STL header files (or any header files, for that matter) included in applySequence.h,
so it is actually not limited to use with an STL container. However, it does
make assumptions (primarily, the name and behavior of the iterator) that
apply to STL sequences. Comment
You can see there is
more than one version of apply( ), further illustrating overloading
of function templates. Although these templates allow any type of return value
(which is ignored, but the type information is required to match the
pointer-to-member), each version takes a different number of arguments, and
because itÂ’s a template, those arguments can be of any type. The only
limitation here is that there’s no “super template” to create templates for
you; you must decide how many arguments will ever be required. Comment
To test the various
overloaded versions of apply( ), the class Gromit is created containing functions with different
numbers of arguments: Comment
Now you can use the apply( )
template functions to apply the Gromit member functions to a vector<Gromit*>,
like this: Comment
Although the
definition of apply( ) is somewhat complex and not something youÂ’d
ever expect a novice to understand, its use is remarkably clean and simple, and
a novice could easily use it knowing only what it is intended to
accomplish, not how. This is the type of division you should strive for
in all your program components: The tough details are all isolated on the
designerÂ’s side of the wall. Users are concerned only with accomplishing their
goals and donÂ’t see, know about, or depend on details of the underlying
implementation. WeÂ’ll explore even more flexible ways to apply functions to
sequences in the next chapter. Comment
We mentioned earlier
that an ordinary function overload of min( ) is preferable to using
the template. If a function already exists to match a function call, why generate
another? In the absence of ordinary functions, however, it is possible that
overloaded function templates can lead to ambiguities. To minimize the chances
of this, an ordering is defined for function templates that chooses the most
specialized template, if such exists. A function template is considered
more specialized than another if every possible list of arguments that matches
it also matches the other, but not the other way around. Consider the following
function template declarations, taken from an example in the C++ standard
document: Comment
The first template can
be matched with any type. The second template is more specialized than the
first because only pointer types match it. In other words, you can look upon
the set of possible calls that match the second template as a subset of the
first. A similar relationship exists between the second and third template
declarations above: the third can only be called for pointers to const,
but the second accommodates any pointer type. The following program illustrates
these rules. Comment
The call f(&i)
certainly matches the first template, but since the second is more specialized,
it is called. The third canÂ’t be called in this case since the pointer is not a
pointer to const. The call f(&j) matches all three templates
(for example, T would be const int in the second template), but
again, the third template is more specialized, so it is used instead. Comment
If there is no “most
specialized” template among a set of overloaded function templates, an
ambiguity remains and the compiler will report an error. That is why this
feature is called a “partial ordering”—it may not be able to resolve all
possibilities. Similar rules exist for class templates (see the section
“Partial specialization” below). Comment
The term specialization
has a specific, template-related meaning in C++. A template definition is, by
its very nature, a generalization, because it describes a family of
functions or classes in general terms. When template arguments are supplied,
the result is a specialization of the template, because it fixes a unique instance
out of the many possible instances of the family of functions or classes. The min
function template seen at the beginning of this chapter is a generalization of
a minimum-finding function, because the type of its parameters is not
specified. When you supply the type for the template parameter, whether
explicitly or implicitly via argument deduction, the resultant code generated
by the compiler (for example, min<int>) is a specialization of the
template. The code generated is also considered an instantiation of the
template, of course, as are all code bodies generated by the template facility.
Comment
You can also provide
the code yourself for a given template specialization, should the need arise.
Providing your own template specializations is often needed with class
templates, but we will begin with the min function template to introduce
the syntax. Comment
Recall that in MinTest.cpp
earlier in this chapter we introduced the following ordinary function:
This was so that a
call to min would compare strings and not addresses. Although it would
provide no advantage in this case, we could define instead a const char*
specialization for min, as in the following program: Comment
The “template<>”
prefix tells the compiler that what follows is a specialization of a template.
The type for the specialization must appear in angle brackets immediately
following the function name, as it normally would in an explicitly-specified
call. Note that we carefully substitute const char* for T
in the explicit specialization. Whenever the original template specifies const
T, that const modifies the whole type T. It is the
pointer to a const char* that is const. Therefore we must write const
char* const in place of const T in the specialization. When the
compiler sees a call to min with const char* arguments in
the program, it will instantiate our const char* version of min so
it can be called. The two calls to min in this program call the same
specialization of min. Comment
Explicit
specializations tend to be more useful for class templates than for function
templates. When you provide a full specialization for a class template, though,
you may need to implement all the member functions. This is because you are
providing a separate class, and client code may expect the complete interface
to be implemented. Comment
The standard library
has an explicit specialization for vector when it is used to hold objects
of type bool. As you saw earlier in this chapter, the declaration for
the primary vector class template is:
To specialize for
objects of type bool, you could declare an explicit specialization as
follows:
Again, this is quickly
recognized as a full, explicit specialization because of the template<>
prefix and because all the primary templateÂ’s parameters are satisfied by the
argument list appended to the class name. The purpose for vector<bool>
is to allow library implementations to save space by packing bits into integers. Comment
It turns out that vector<bool>
is a little more flexible than we have described, as seen in the next section.
Class templates can
also be partially specialized, meaning that at least one of the template
parameters is left “open” in some way in the specialization. This is actually
what vector<bool> does; it specifies the object type (bool),
but leaves the allocator type unspecified. Here is the actual declaration of vector<bool>:
You can recognize a
partial specialization because non-empty parameter lists appear in angle
brackets both after the template keyword (the unspecified parameters) and after
the class (the specified arguments). Because of the way vector<bool>
is defined, a user can provide a custom allocator type, even though the
contained type of bool is fixed. In other words, specialization, and
partial specialization in particular, constitute a sort of “overloading” for
class templates. Comment
The rules that
determine which template is selected for instantiation are similar to the
partial ordering for function templates—the “most specialized” template is
selected. An illustration follows. (The string in each f( ) member
function below explains the role of each template definition.) Comment
//:
C05:PartialOrder2.cpp
// Reveals partial
ordering of class templates
#include
<iostream>
using namespace std;
template<class T,
class U> class C {
public:
void f() {
cout <<
"Primary Template\n";
}
};
template<class
U> class C<int, U> {
public:
void f() {
cout <<
"T == int\n";
}
};
template<class
T> class C<T, double> {
public:
void f() {
cout <<
"U == double\n";
}
};
template<class T,
class U> class C<T*, U> {
public:
void f() {
cout <<
"T* used \n";
}
};
template<class T,
class U> class C<T, U*> {
public:
void f() {
cout <<
"U* used\n";
}
};
template<class T,
class U> class C<T*, U*> {
public:
void f() {
cout <<
"T* and U* used\n";
}
};
template<class
T> class C<T, T> {
public:
void f() {
cout <<
"T == U\n";
}
};
int main() {
C<float,
int>().f(); // 1: Primary template
C<int,
float>().f(); // 2: T == int
C<float,
double>().f(); // 3: U == double
C<float, float>().f();
// 4: T == U
C<float*,
float>().f(); // 5: T* used [T is float]
C<float,
float*>().f(); // 6: U* used [U is float]
C<float*,
int*>().f(); // 7: T* and U* used [float,int]
// The following are
ambiguous:
// 8: C<int,
int>().f();
// 9: C<double,
double>().f();
// 10: C<float*,
float*>().f();
// 11: C<int,
int*>().f();
// 12: C<int*,
int*>().f();
} ///:~
As you can see, you
can partially specify template parameters according to whether they are pointer
types, or whether they are equal. When the T* specialization is used,
such as is the case in line 5, T itself is not the top-level pointer
type that was passed—it is the type that the pointer refers to (float, in
this case). The T* specification is a pattern to allow matching against pointer
types. If you were to use int** as the first template argument, T
would be int*. Line 8 is ambiguous because having the first parameter as
an int vs. having the two parameters equal are independent issues—one is
not more specialized than the other. Similar logic applies to lines 9 through
12. Comment
You can easily derive
from a class template, and you can create a new template that instantiates and
inherits from an existing template. If the vector template does most everything
you want, for example, but in a certain application youÂ’d also like a version that
can sort itself, you can easily reuse the vector code. The following
example derives from vector<T> and adds sorting. Comment
The Sorted
template imposes a restriction on all but one of the classes for which it is
instantiated: they must contain a > operator. It works correctly only
with non-pointer objects (including objects of built-in types). The full
specialization compares the elements using strcmp( ) to sort
vectors of char* according to the null-terminated strings to which they
refer. Comment
HereÂ’s a driver for Sorted.h
that uses the randomizer introduced earlier in the chapter: Comment
Each of the template
instantiations above uses a different version of the template. Sorted<int>
uses the primary template. Sorted<string*> uses the partial
specialization for pointers. Last, Sorted<char*> uses the full
specialization for char*. Without this full specialization, you could be
fooled into thinking that things were working correctly because the words
array would still sort out to “a big dog is running” since the partial
specialization would end up comparing the first character of each array.
However, words2 would not sort correctly. Comment
Whenever a class
template is instantiated, the code from the class definition for the particular
specialization is generated, along with all the member functions that are
called in the program. Only the member functions that are actually called are
generated. This is a good thing, as the following program makes clear: Comment
Here, even though the
template Z purports to use both f( ) and g( )
member functions of T, the fact that the program compiles shows you that
it only generates Z<X>::a( ) when it is explicitly called for
zx. (If Z<X>::b( ) were also generated at the same
time, a compile-time error message would be generated, because it would attempt
to call X::g( ), which doesnÂ’t exist.) Similarly, the call to zy.b( )
doesnÂ’t generate Z<Y>::a( ). As a result, the Z
template can be used with X and Y; whereas if all the member
functions were generated when the class was first created the use of many
templates would significantly limited. Comment
Suppose you have a
container, a Stack say, and you use specializations for int, int*,
and char*. Three versions of Stack code will be generated and
linked as part of your program. One of the reasons for using a template in the
first place is so you donÂ’t have to replicate code by hand; but code still gets
replicated—it’s just the compiler that does it instead of you. You can factor
the bulk of the implementation for storing pointer types into a single class by
using a combination of full and partial specialization. The key is to fully
specialize for void* and then derive all other pointer types from the void*
implementation so the common code can be shared. The program below illustrates
this technique. Comment
This simple stack
expands as it fills its capacity. The void* specialization stands out as
a full specialization by virtue of the template<> prefix (that is,
the template parameter list is empty). As mentioned earlier, it is necessary to
implement all member functions in a class template specialization. The savings
occurs with all other pointer types. The partial specialization for other
pointer types derives from Stack<void*> privately, since we are
merely using Stack<void*> for implementation purposes, and do not
wish to expose any of its interface directly to the user. The member functions
for each pointer instantiation are small forwarding functions to the
corresponding functions in Stack<void*>. Hence, whenever a pointer
type other than void* is instantiated, it is a fraction of the size it
would have been had the primary template alone been used. Here is a driver program: Comment
For convenience we
have included two emptyStack function templates. Since function
templates donÂ’t support partial specialization, we provide overloaded
templates. The second version of emptyStack is more specialized than the
first, so it is chosen whenever pointer types are used. Three class templates
are instantiated in this program: Stack<int>, Stack<void*>,
and Stack<int*>. Stack<void*> is implicitly
instantiated because Stack<int*> derives from it. If a program
uses instantiations for many pointer types, the savings in code size over just
using a single Stack template can be substantial. Comment
When the compiler
encounters an identifier it must determine the type and scope (and in the case
of variables, the lifetime) of the entity the identifier represents. This is
common knowledge among software developers, but the plot thickens when
templates are involved. Because not everything is known about a template when
its definition is first seen by the compiler, the compiler must hold off until
the template is instantiated before it can determine whether it is being used
properly. This predicament leads to a two-phase process for template compilation.
Comment
In the first phase the
compiler parses the template definition looking for obvious syntax errors and
resolving all the names it can. The names it can resolve during parsing are
those that do not depend on template parameters, which the compiler takes care
of through normal name lookup means (and also through argument-dependent
lookup, discussed below, if necessary). The names it canÂ’t resolve are the
so-called dependent names, which are names that in some way depend on
template parameters. These canÂ’t be resolved until the template is instantiated
with its actual arguments. Instantiation, therefore, is the second phase of
template compilation. During this second phase, the compiler determines whether
an explicit specialization of the template in question needs to be used instead
of the primary template. Comment
Before you see an
example, two more terms need to be defined. A qualified name is a name
with a class-name prefix, a name with an object name and a dot operator, or a
name with a pointer to an object and an arrow operator. Examples of qualified
names are found in the following expressions: Comment
We have used qualified
names many times in this book, and most recently in connection with the typename
keyword. These are called qualified names because the target names (like f
above) are explicitly associated with a class, which tells the compiler where
to look for the declarations of those names. Comment
The other term to
discuss is argument-dependent lookup (ADL), which is a technique originally designed to
simplify using non-member function calls (including operators) declared in
namespaces. Consider the following simple code excerpt: Comment
Note that there is no using
namespace std; directive, which is the typical practice inside header
files, for example. Without such a directive, it is necessary to use the std::
qualifier on the items that are in the std namespace. We have, however,
not qualified everything from std that we are using. Can you see what we
have left unqualified? Comment
We have not specified
which operator functions to use. We want the following to happen, but we donÂ’t
want to have to type it!
To make the original
output statement work as desired, ADL specifies that when an unqualified
function call appears and its declaration is not in (normal) scope, the
namespaces (or class scopes) of each of its arguments are searched for a
matching function declaration. In the original statement, the first function
call is: Comment
Since there is no such
function in scope in our original excerpt, the compiler notes that this
functionÂ’s first argument (std::cout) is in the namespace std; so
it adds that namespace to the list of scopes to search for a unique function
that best matches the signature operator<<(std::ostream&,
std::string). It finds this function declared in the std namespace
via the <string> header, so that is the function that is called.
Namespaces would be very inconvenient without ADL. (But note that, in general,
ADL brings in all declarations of the name in question from all eligible
namespaces—if there is no best match, an ambiguity will result.) To turn off
ADL, you can enclose the function name in parentheses: Comment
Now consider the
following program, from a presentation by Herb Sutter:
The only compiler we
have that gets this correct right out of the box is the Edison Design Group
front end. (A number of compilers use this front end, including Comeau C++.) The
output should be: Comment
because f is a
non-dependent name that can be resolved early by looking in the context where
the template is defined, when only f(double) is in scope. Unfortunately,
there is a lot of code in existence that depends on the non-standard behavior
of binding the call to f(1) inside g( ) to the latter f(int),
so compiler writers have been reluctant to make the change. (Some compilers, such
as the Metrowerks compiler, have an option to enable the correct lookup
behavior.) Comment
Here is a more
detailed example, also based on an example from Herb Sutter:
The output from this
program should be:
Looking at the
declarations inside of X::f( ), we observe the following:
·
The return type of X::f( ),
which is E, is not a dependent name, so it is looked up when the
template is parsed, and the typedef naming E as a double
is found. This may seem strange, since with non-template classes the
declaration of E in the base class would be found first, but those are
the rules. (The base class, Y, is a dependent base class, so it
canÂ’t be searched at template definition time). Comment
·
The call to g( ) is
also non-dependent, since there is no mention of T. If g had
parameters that were of class type of defined in another namespace, ADL would
take over, since there is no g with parameters in scope. As it is, this
call matches the global declaration of g( ). Comment
·
The call this->h( )
is a qualified name, and the object that qualifies it (this) refers to
the current object, which is of type X, which in turn depends on the
name Y<T> by inheritance. There is no function h( )
inside of X, so lookup will naturally want to search the scope of XÂ’s
base class, Y<T>. Since this is a dependent name, it is looked up
at instantiation time, when Y<T> can be reliably known (including
any potential specializations that might have been written after the definition
of X); so it calls Y<int>::h( ). Comment
·
The declarations of t1 and t2
are dependent, of course. Comment
·
The call to operator<<(cout,
t1) is dependent, since t1 is of type T. This is looked up
later when T is int, and the inserter for int is found in std.
Comment
·
The unqualified call to swap( )
is dependent because its arguments are of type T. This ultimately causes
a global swap(int&, int&) to be instantiated, of course. Comment
·
The qualified call to std::swap( )
is not dependent, because std is a fixed namespace; so the
compiler knows to look there for the proper declaration. (The qualifier on the
left of the “::” must mention a template parameter for a qualified name
to be considered dependent.) The std::swap( ) function template
later generates std::swap(int&, int&), at instantiation time. No
more dependent names remain in X<T>::f( ). Comment
To clarify and
summarize: name lookup is done at the point of instantiation if the name is
dependent, except that for unqualified dependent names the normal name lookup
is also attempted early, at the point of definition. All non-dependent names in
templates are looked up early, at the time the template definition is parsed.
(If necessary, another lookup occurs at instantiation time, when the type of
the actual argument is known.) Comment
(Whew!) If you have
studied this example to the point that you understand it, prepare yourself for
yet another surprise in the next section when friend declarations enter
the picture.
A friend function
declaration inside a class allows a non-member function to access non-public
members of that class. If the friend function name is qualified, it will of
course be found in the namespace or class that qualifies it. If it is
unqualified, however, the compiler must make an assumption about where the
definition of the friend function will be, since all identifiers must have a
unique scope. The expectation is that the function will be defined in the
nearest enclosing namespace (non-class) scope that contains the class granting
friendship. Often this is just the global scope. The following non-template
example clarifies this issue. Comment
The declaration of f( )
inside the Friendly class is unqualified, so the compiler will expect to
be able to eventually link that declaration to a definition at file scope (the
namespace scope that contains Friendly in this case). That definition
appears after the definition of the function h( ). The linking of
the call to f( ) inside h( ) to the same function is a
separate matter, however. This is resolved by ADL. Since the argument of f( )
inside h( ) is a Friendly object, the Friendly class
is searched for a declaration of f( ), which succeeds. If the call
were f(1) instead (which makes some sense since 1 can be implicitly
converted to Friendly(1)), the call should fail, since there is no hint
of where the compiler should look for the declaration of f( ). The
EDG compiler correctly complains that f is undefined in that case. Comment
Now suppose that Friendly
and f are both templates, as in the following program.
First notice that
angle brackets in the declaration of f inside Friendly. This is
necessary to tell the compiler that f is a template. Otherwise, the
compiler will look for an ordinary function named f and of course not
find it. We could have inserted the template parameter (<T>) in
the brackets, but it is easily deduced from the declaration. Comment
The forward
declaration of the function template f before the class definition is
necessary, even though it wasnÂ’t in the previous example when f was a
not a template; the language specifies that friend function templates must be
previously declared. Of course, to properly declare f, Friendly
must also have been declared, since f takes a Friendly argument,
hence the forward declaration of Friendly in the beginning. We could
have placed the full definition of f right after the initial declaration
of Friendly instead of separating its definition and declaration, but we
chose instead to leave it in a form that more closely resembles the previous
example. Comment
One last option
remains for using friends inside templates: fully define them inside the class
itself. Here is how the previous example would appear with that change:
There is an important
difference between this and the previous example: f is not a template
here, but is an ordinary function. (Remember that angle brackets were necessary
before to imply that f was a template.) Every time the Friendly
class template is instantiated, a new, ordinary function overload is created
that takes an argument of the current Friendly specialization. This is
what Dan Saks has called “making new friends.” This is the most convenient way to define friend
functions for templates. Comment
To make this perfectly
clear, suppose you have a class template to which you want to add non-member
operators as friends. Here is a class template that simply holds a generic
value:
Without understanding
the likes of the previous examples in this section, novices find themselves
frustrated because they canÂ’t get a simple stream output inserter to work. If
you donÂ’t define your operators inside the definition of Box, you must
provide the forward declarations we showed earlier: Comment
Here we are defining
both an addition operator and an output stream operator. The main program
reveals a disadvantage of this approach: you canÂ’t depend on implicit
conversions (see the expression b1 + 2) because templates do not provide
them. Using the in-class, non-template approach is shorter and more robust: Comment
Because the operators
are normal functions (overloaded for each specialization of Box—just int
in this case, of course), implicit conversions are applied as normal; so the
expression b1 + 2 is valid. Comment
You can be precise as
to which specializations of a template are friends of a class. In the examples
in the previous section, only the specialization of the function template f
with the same type that specialized Friendly was a friend. For example,
only the specialization f<int>(const Friendly<int>&) is
a friend of the class Friendly<int>. This was accomplished by
using the template parameter for Friendly to specialize f in its
friend declaration. If we had wanted to, we could have made a particular, fixed
specialization of f a friend to all instances of Friendly, like
this: Comment
By using double
instead of T, the double specialization of f has access to
the non-public members of any Friendly specialization. The
specialization f<double>( ) still isnÂ’t instantiated unless
it is explicitly called, of course. Comment
Likewise, if you were
to declare a non-template function with no parameters dependent on T,
that single function would be a friend to all instances of Friendly: Comment
As always, since g(int)
is unqualified, it must be defined at file scope (the namespace scope
containing Friendly). Comment
It is also possible to
arrange for all specializations of f to be friends for all
specializations of Friendly, with a so-called friend template, as
follows:
Since the template
argument for the friend declaration is independent of T, any combination
of T and U is allowed, achieving the friendship objective. Like
member templates, friend templates can appear within non-template classes as
well. Comment
Since language is a
tool of thought, new language features tend to spawn new programming
techniques. In this section we cover some commonly-used template programming
idioms that have emerged in the years since templates were added to the C++
language. Comment
The traits template
technique, pioneered by Nathan Myers, is a means of bundling type-dependent declarations
together. In essence, using traits allows you to “mix and match” certain types
and values with contexts that use them in a flexible manner, while keeping your
code readable and maintainable. Comment
The simplest example
of a traits template is the numeric_limits class template defined in <limits>.
The primary template is defined as follows:
The <limits>
header defines specializations for all fundamental, numeric types (in which
case the member is_specialized is set to true). To obtain the
base for the double version of your floating-point number system, for
example, you can use the expression numeric_limits<double>::radix.
To find the smallest integer value available, you can use numeric_limits<int>::min( ).
Not all members of numeric_limits apply to all fundamental types, of
course. (For example, epsilon( ) is only for floating-point types.)
Comment
The values that will
always be integral are static data members of numeric_limits; those that
may not be integral, such as the minimum value for float, are
implemented as static inline member functions. This is because C++ allows only integral
static data member constants to be initialized inside a class definition. Other
members, such as floating-point values, must be initialized at file scope
outside the class definition, which is not appropriate in a header file. Since the
needed value in that case will be placed in an implementation (.cpp) file, the
value will not be available for compile-time optimization. Inline member
functions of a class template, on the other hand, can be included in a header
file, and thus facilitate compile-time optimization. Comment
In Chapter 3 you saw
how traits are used to control the character-processing functionality used by
the string classes. The classes std::string and std::wstring are
specializations of the std::basic_string template, which is defined as
follows:
The template parameter
charT represents the underlying character type, which is usually either char
or wchar_t. The primary char_traits template is typically empty, and
specializations for char and wchar_t are provided by the standard
library. Here is the specification of the specialization char_traits<char>
according to the C++ standard: Comment
template<>
struct
char_traits<char> {
typedef char char_type;
typedef int
int_type;
typedef streamoff
off_type;
typedef streampos
pos_type;
typedef mbstate_t
state_type;
static void
assign(char_type& c1, const char_type& c2);
static bool eq(const
char_type& c1, const char_type& c2);
static bool lt(const
char_type& c1, const char_type& c2);
static int
compare(const char_type* s1,
const char_type* s2, size_t n);
static size_t
length(const char_type* s);
static const
char_type* find(const char_type* s,
size_t n,
const char_type& a);
static char_type*
move(char_type* s1,
const char_type* s2, size_t n);
static char_type*
copy(char_type* s1,
const char_type* s2, size_t n);
static char_type*
assign(char_type* s, size_t n,
char_type a);
static int_type
not_eof(const int_type& c);
static char_type
to_char_type(const int_type& c);
static int_type
to_int_type(const char_type& c);
static bool
eq_int_type(const int_type& c1,
const int_type& c2);
static int_type
eof();
};
These functions are
used by the basic_string class template for character-based operations
common to string processing. When you declare a string variable, such
as: Comment
you are actually
declaring s as follows (because of the default template arguments in the
specification of basic_string):
Because the character
traits have been separated from the basic_string class template, you can
supply a custom traits class to replace std::char_traits. The following
example illustrates this flexibility. Comment
//: C05:PoohCorner.cpp
// Illustrates traits
classes
#include
<iostream>
using namespace std;
// Item classes
(traits of guests):
class Water {
public:
friend ostream&
operator<<(ostream& os, const Water&) {
return os <<
"Water";
}
};
class Milk {
public:
friend ostream&
operator<<(ostream& os, const Milk&) {
return os <<
"Milk";
}
};
class Honey {
public:
friend ostream&
operator<<(ostream& os, const Honey&) {
return os <<
"Honey";
}
};
class Cookies {
public:
friend ostream&
operator<<(ostream& os, const Cookies&) {
return os <<
"Cookies";
}
};
// Guest classes:
class Bear {
public:
friend ostream&
operator<<(ostream& os, const Bear&) {
return os <<
"Pooh";
}
};
class Boy {
public:
friend ostream&
operator<<(ostream& os, const Boy&) {
return os <<
"Christopher Robin";
}
};
// Primary traits template
(empty—could hold common types)
template<class
Guest>
class GuestTraits;
// Traits
specializations for Guest types
template<>
class
GuestTraits<Bear> {
public:
typedef Water
beverage_type;
typedef Honey
snack_type;
};
template<>
class GuestTraits<Boy>
{
public:
typedef Milk
beverage_type;
typedef Cookies
snack_type;
};
// A custom traits
class
class MixedUpTraits {
public:
typedef Milk
beverage_type;
typedef Honey
snack_type;
};
// The Guest template
(uses a traits class)
template< class
Guest, class traits = GuestTraits<Guest> >
class PoohCorner {
Guest theGuest;
typedef typename
traits::beverage_type beverage_type;
typedef typename
traits::snack_type snack_type;
beverage_type bev;
snack_type
snack;
public:
PoohCorner(const
Guest& g)
: theGuest(g),
bev(beverage_type()),
snack(snack_type())
{}
void entertain() {
cout <<
"Entertaining " << theGuest
<< " serving " << bev
<< " and " << snack << endl;
}
};
int main() {
Boy cr;
PoohCorner<Boy>
pc1(cr);
pc1.entertain();
Bear pb;
PoohCorner<Bear> pc2(pb);
pc2.entertain();
PoohCorner<Bear,
MixedUpTraits> pc3(pb);
pc3.entertain();
} ///:~ Comment
In this program,
instances of the guest classes Boy and Bear are served items
appropriate to their tastes. Boys like milk and cookies and Bears
like water and honey. This association of guests to items is done via
specializations of a primary (empty) traits class template. The default
arguments to PoohCorner ensure that guests get their proper items, but
you can override this by simply providing a class that meets the requirements
of the traits class, as we do with the MixedUpTraits class above. The
output of this program is: Comment
Using traits provides
two key advantages: (1) it allows flexibility in pairing objects with
associated attributes or functionality, and (2) it keeps template parameter
lists small and readable. If 30 types were associated with a guest, it would be
inconvenient to have to specify all 30 arguments directly in each PoohCorner
declaration. Factoring the types into a separate traits class simplifies things
considerably. Comment
The traits technique
is also used in implementing streams and locales, as we showed in Chapter 4.
If you inspect the char_traits
specialization for wchar_t, youÂ’ll see that it is practically identical
to its char counterpart:
template<>
struct
char_traits<wchar_t> {
typedef wchar_t
char_type;
typedef wint_t
int_type;
typedef streamoff
off_type;
typedef wstreampos
pos_type;
typedef mbstate_t
state_type;
static void assign(char_type&
c1, const char_type& c2);
static bool eq(const
char_type& c1, const char_type& c2);
static bool lt(const
char_type& c1, const char_type& c2);
static int
compare(const char_type* s1,
const char_type* s2, size_t n);
static size_t
length(const char_type* s);
static const
char_type* find(const char_type* s,
size_t n,
const char_type& a);
static char_type*
move(char_type* s1,
const char_type* s2, size_t n);
static char_type*
copy(char_type* s1,
const char_type* s2, size_t n);
static char_type*
assign(char_type* s, size_t n,
char_type a);
static int_type
not_eof(const int_type& c);
static char_type
to_char_type(const int_type& c);
static int_type
to_int_type(const char_type& c);
static bool
eq_int_type(const int_type& c1,
const int_type& c2);
static int_type
eof();
};
The only real
difference between the two versions is the set of types involved (char
and int vs. wchar_t and wint_t). The functionality
provided is the same. This highlights the fact that traits classes are indeed
for traits, and therefore the things that change between related traits
classes are usually types and constant values, or fixed algorithms that use
type-related template parameters. Traits classes tend to be templates
themselves, since the types and constants they contain are seen as
characteristics of the primary template parameter(s) (for example, char and
wchar_t). Comment
It is also useful to
be able to associate functionality with template arguments, so that client
programmers can easily customize behavior when they code. The following version
of the PoohCorner program, for instance, supports different types of
entertainment: Comment