Don't hesitate to send in feedback: send an e-mail if you like the C++ Annotations; if you think that important material was omitted; if you find errors or typos in the text or the code examples; or if you just feel like e-mailing. Send your e-mail to Frank B. Brokken.Please state the document version you're referring to, as found in the title (in this document: 8.3.1) and please state chapter and paragraph name or number you're referring to.
All received mail is processed conscientiously, and received suggestions for improvements will usually have been processed by the time a new version of the Annotations is released. Except for the incidental case I will normally not acknowledge the receipt of suggestions for improvements. Please don't interpret this as me not appreciating your efforts.
Templates can not only be constructed for functions but also for complete
classes. Consider constructing a
class template when a class should be able
to handle different types of data. Class templates are frequently used in
C++: chapter 12 discusses data structures like vector,
stack and queue, that are implemented as class templates. With class
templates, the algorithms and the data on which the algorithms operate are
completely separated from each other. To use a particular data structure in
combination with a particular data type only the data type needs to be
specified when defining or declaring a class template object (as in
stack<int> iStack).
In this chapter constructing and using class templates is discussed. In a sense, class templates compete with object oriented programming (cf. chapter 14), that uses a mechanism resembling that of templates. Polymorphism allows the programmer to postpone the implementation of algorithms by deriving classes from base classes in which algorithms are only partially implemented. The actual definition and processing of the data upon which the algorithms operate may be postponed until derived classes are defined. Likewise, templates allow the programmer to postpone the specification of the data upon which the algorithms operate. This is most clearly seen with abstract containers, that completely specify the algorithms and that at the same time leave the data type on which the algorithms operate completely unspecified.
The correspondence between class templates and polymorphic classes is
well known. In their book C++ Coding Standards (Addison-Wesley, 2005)
Sutter and Alexandrescu (2005) refer to
static polymorphism
and
dynamic polymorphism.
Dynamic polymorphism is what we use when overriding virtual members.
Usingem vtables the function that is actually called depends on the type
of object a (base) class pointer points at. Static polymorphism is
encountered in the context of templates. Depending on the actual types, the
compiler creates the code, compile time, that's appropriate for those
particular types. There's no need to consider static and dynamic polymorphism
as mutually exlusive variants of polymorphism. Rather, both can be used
together, combining their strengths. A warning is in place, though. When a
class template defines virtual members all virtual members are
instantiated for every instantiated type. All virtual members must be
instantiated (whether they are used or not) as the compiler must construct the
vtable for each data type for which an object of the class is
instantiated.
Generally, class templates are easier to use than polymorphism. It is
certainly easier to write stack<int> istack to create a stack of ints
than to derive a new class Istack: public stack and to implement all
necessary member functions defining a similar stack of ints using object
oriented programming. On the other hand, for each different type for which an
object of a class template is defined another, possibly complete class must be
reinstantiated. This is not required in the context of object oriented
programming where derived classes use, rather than copy, the functions
that are already available in their base classes (but see also section
21.10).
Previously we've already used class templates. Objects like vector<int> vi
and vector<string> vs are commonly used. The data types for which these
templates are defined and instantiated are an inherent part of such container
types. It is stressed that it is the combination of a class template type
and its template parameter(s), rather than the mere class template's type
that defines or generates a
type. So vector<int> is a type as is
vector<string>. Such types could very well be represented by typedefs:
typedef std::vector<int> IntVector;
typedef std::vector<std::string> StringVector;
IntVector vi;
StringVector vs;
The new class implements a
circular queue. a circular queue has a fixed
number of max_size elements. New elements are inserted at its back and
only its head and tail elements can be accessed. Only the head element can be
removed from a circular queue. Once n elements have been appended the next
element will again be inserted at the queue's (physical) first position. The
circular queue allows insertions until it holds max_size elements. As long
as a circular queue contains at least one element elements may be removed from
it. Trying to remove an element from an empty circular queue or to add another
element to a full circular queue results in exceptions being thrown. In
addition to other constructors a circular queue must offer a constructor
initializing its objects for max_size elements. This constructor must make
available the memory for the max_size elements but must not call those
elements default constructors (hinting at the use of the placement new
operator). A circular queue should offer value semantics as well as a move
constructor.
Please note that in the above description the actual data type that is used for the circular queue is nowhere mentioned. This is a clear indication that our class could very well be defined as a class template. Alternatively, the class could be defined for some concrete data type which is then abstracted when converting the class to a class template.
In this section the class template CirQue (circular
queue) is constructed, having one template type parameter Data
representing the data type that is stored in the circular queue. The
outline of the interface of this class template looks like this:
template<typename Data>
class CirQue
{
// member declarations
};
A class template's definition starts like a function template's
definition:
template, starting a template definition or
declaration.
template: a
list containing one or more comma-separated elements called the
enclosed list is called the
template parameter list. Template parameter lists may have multiple
elements, like this:
typename Type1, typename Type2, typename Type3
When a class template defines multiple template type parameters they are
matched in sequence with the list of template type arguments provided when
defining objects of such a class template. Example:
template <typename Type1, typename Type2, typename Type3>
class MultiTypes
{
...
};
MultiTypes<int, double, std::string> multiType;
// Type1 is int, Type2 is double, Type3 is std::string
Data for CirQue). It is a formal (type) name, like the formal types
used in function template parameter lists.
CirQue class template has been defined it can be used to
create all kinds of circular queues. As one of its constructors expects a
size_t argument defining the maximum number of elements that can be stored
in the circular queue circular queues could be defined like this:
CirQue<int> cqi(10); // max 10 ints
CirQue<std::string> cqstr(30); // max 30 strings
As noted in the introductory section of this chapter the combination of
name of the class template and the data type for which it is instantiated
defines a data type. Also note the similarity between defining a
std::vector (of some data type) and a CirQue (of some data type).
Like std::map containers class templates may be defined with multiple
template type parameters.
Back to CirQue. A CirQue must be capable of storing max_size
Data elements. These elements are eventually stored in memory pointed at by
a pointer Data *d_data, initially pointing to raw memory. New elements are
added at the backside of the CirQue. A pointer Data *d_back is used to
point to the location where the next element will be stored. Likewise, Data
*d_front points to the location of the CirQue's first element. Two
size_t data members are used to monitor the filling state of the
CirQue: d_size represents the number of elements currently stored in
the CirQue, d_maxSize represents the maximum number of elements that
the CirQue can contain. Thus, the CirQue's data members are:
size_t d_size;
size_t d_maxSize;
Data *d_data;
Data *d_front;
Data *d_back;
The class CirQue offers various member functions. Normal design
principles should be adhered to when constructing class template
members. Class template type parameters should preferably be defined as
Type const &, rather than Type, to prevent unnecessary copying of
large data structures. Template class constructors should use member
initializers rather than member assignment within the body of the
constructors. Member function definitions should preferably not be provided
in-class but below the class interface. Since class template member functions
are function templates their definitions should be provided in the header
file offering the class interface. They may be given the inline
attribute.
CirQue declares the following (public) members (their definitions are
provided as well; all definitions are provided below the class interface):
explicit CirQue(size_t maxSize = 0):Constructor initializing aCirQuecapable of storingmax_size Dataelements. As the constructor's parameter is given a default argument value this constructor can also be used as a default constructor, allowing us to define, e.g., vectors ofCirQues. The constructor initializes theCirqueobject'sd_datamember to a block of raw memory andd_frontandd_backare initialized tod_data. As class template member functions are themselves function templates their implementations outside of the class template's interface must start with the class template's template header. Here is the implementation of theCirQue(size_t)constructor:template<typename Data> CirQue<Data>::CirQue(size_t maxSize) : d_size(0), d_maxSize(maxSize), d_data( maxSize == 0 ? 0 : static_cast<Data *>( operator new(maxSize * sizeof(Data))) ), d_front(d_data), d_back(d_data) {}
CirQue(CirQue<Data> const &other):The copy constructor has no special features. It uses a private support memberincto incrementd_back(see below) and placement new to copy the other'sDataelements to the current object. The implementation of the copy constructor is straightforward:template<typename Data> CirQue<Data>::CirQue(CirQue<Data> const &other) : d_size(other.d_size), d_maxSize(other.d_maxSize), d_data( d_maxSize == 0 ? 0 : static_cast<Data *>( operator new(d_maxSize * sizeof(Data))) ), d_front(d_data + (other.d_front - other.d_data)) { Data const *src = other.d_front; d_back = d_front; for (size_t count = 0; count != d_size; ++count) { new(d_back) Data(*src); d_back = inc(d_back); if (++src == other.d_data + d_maxSize) src = other.d_data; } }
CirQue(CirQue<Data> const &&tmp):The move constructor merely initializes the current object'sd_datapointer to 0 and swaps (see the memberswap, below) the temporary object with the current object.CirQue's destructor will inspectd_dataand will immediately return when it's zero. Implementation:template<typename Data> CirQue<Data>::CirQue(CirQue<Data> const &&tmp) : d_data(0) { swap(const_cast<CirQue<Data> &>(tmp)); }
~CirQue():The destructor inspects thed_datamember. If it is zero then nothing has been allocated and the destructor immediately returns. This may occur in two situations: the circular queue contains no elements or the information was grabbed from a temporary object by some move operation, setting the temporary'sd_datamember to zero. Otherwised_sizeelements are destroyed by explicitly calling their destructors followed by returning the element's raw memory to the common pool. Implementation:template<typename Data> CirQue<Data>::~CirQue() { if (d_data == 0) return; for (; d_size--; ) { d_front->~Data(); d_front = inc(d_front); } operator delete(d_data); }
CirQue &operator=(CirQue<Data> const &other):The copy assignment operator has a standard implementation:template<typename Data> CirQue<Data> &CirQue<Data>::operator=(CirQue<Data> const &rhs) { CirQue<Data> tmp(rhs); swap(tmp); }
CirQue &operator=(CirQue<Data> const &&tmp):The move assignment operator also has a standard implementation. As its implementation merely callsswapit is defined as an inline function template:template<typename Data> inline CirQue<Data> &CirQue<Data>::operator=(CirQue<Data> const &&tmp) { swap(const_cast<CirQue<Data> &>(tmp)); }
void pop_front():removes the element pointed at byd_frontfrom theCirQue. Throws an exception if theCirQueis empty. The exception is thrown as aCirQue<Data>::EMPTYvalue, defined by theenum CirQue<Data>::Exception(seepush_back). The implementation is straightforward (explicitly calling the destructor of the element that is removed):template<typename Data> void CirQue<Data>::pop_front() { if (d_size == 0) throw EMPTY; d_front->~Data(); d_front = inc(d_front); --d_size; }
void push_back(Data const &object):adds another element to theCirQue. Throws aCirQue<Data>::FULLexception if theCirQueis full. The exceptions that can be thrown by aCirQueare defined in itsExceptionenum:enum Exception { EMPTY, FULL };A copy of
objectis installed in theCirQue's raw memory using placementnewand itsd_sizeis incremented.template<typename Data> void CirQue<Data>::push_back(Data const &object) { if (d_size == d_maxSize) throw FULL; new (d_back) Data(object); d_back = inc(d_back); ++d_size; }
void swap(CirQue<Data> &other):swaps the currentCirQueobject with anotherCirQue<Data>object;template<typename Data> void CirQue<Data>::push_back(Data const &object) { if (d_size == d_maxSize) throw FULL; new (d_back) Data(object); d_back = inc(d_back); ++d_size; }
Data &back():returns a reference to the element pointed at byd_back(undefined result if theCirQueis empty):template<typename Data> inline Data &CirQue<Data>::back() { return d_back == d_data ? d_data[d_maxSize - 1] : d_back[-1]; }
Data &front():returns reference to the element pointed at byd_front(undefined result if theCirQueis empty);template<typename Data> inline Data &CirQue<Data>::front() { return *d_front; }
bool empty() const:returnstrueif theCirQueis empty;template<typename Data> void CirQue<Data>::push_back(Data const &object) { if (d_size == d_maxSize) throw FULL; new (d_back) Data(object); d_back = inc(d_back); ++d_size; }
bool full() const:returnstrueif theCirQueis full;template<typename Data> void CirQue<Data>::push_back(Data const &object) { if (d_size == d_maxSize) throw FULL; new (d_back) Data(object); d_back = inc(d_back); ++d_size; }
size_t size() const:returns the number of elements currently stored in theCirQue;template<typename Data> void CirQue<Data>::push_back(Data const &object) { if (d_size == d_maxSize) throw FULL; new (d_back) Data(object); d_back = inc(d_back); ++d_size; }
size_t maxSize() const:returns the maximum number of elements that can be stored in theCirQue;template<typename Data> void CirQue<Data>::push_back(Data const &object) { if (d_size == d_maxSize) throw FULL; new (d_back) Data(object); d_back = inc(d_back); ++d_size; }
Finally, the class has one private member, inc, returning a
cyclically incremented pointer into CirQue's raw memory:
template<typename Data>
Data *CirQue<Data>::inc(Data *ptr)
{
++ptr;
return ptr == d_data + d_maxSize ? d_data : ptr;
}
When objects of a class template are instantiated, only the definitions of all the template's member functions that are used must have been seen by the compiler. That characteristic of templates could be refined to the point where each definition is stored in a separate function template definition file. In that case only the definitions of the function templates that are actually needed would have to be included. However, it is hardly ever done that way. Instead, the usual way to define class templates is to define the interface and to define the remaining function templates immediately below the class template's interface (defining some functions inline).
Now that the class CirQue has been defined, it can be used. To use the
class its object must be instantiated for a particular data type. In the
following example it is initialized for data type std::string:
#include "cirque.h"
#include <iostream>
#include <string>
using namespace std;
int main()
{
CirQue<string> ci(4);
ci.push_back("1");
ci.push_back("2");
cout << ci.size() << ' ' << ci.front() << ' ' << ci.back() << '\n';
ci.push_back("3");
ci.pop_front();
ci.push_back("4");
ci.pop_front();
ci.push_back("5");
cout << ci.size() << ' ' << ci.front() << ' ' << ci.back() << '\n';
CirQue<string> copy(ci);
copy.pop_front();
cout << copy.size() << ' ' << copy.front() << ' ' << copy.back() << '\n';
int arr[] = {1, 3, 5, 7, 9};
CirQue<int> ca(arr);
cout << ca.size() << ' ' << ca.front() << ' ' << ca.back() << '\n';
// int *ap = arr;
// CirQue<int> cap(ap);
}
When defining defaults keep in mind that they should be suitable for the
majority of instantiations of the class. E.g., for the class template
CirQue the template's type parameter list could have been altered
by specifying int as its default type:
template <typename Data = int>
Even though default arguments can be specified, the compiler must still be
informed that object definitions refer to templates. When instantiating class
template objects using default template arguments the type specifications may
be omitted but the angle brackets must be retained. Assuming a default
type for the CirQue class, an object of that class may be defined
as:
CirQue<> intCirQue(10);
Default template arguments cannot be specified when defining template
members. So, the definition of, e.g., the push_back member must always
begin with the same template specification:
template <typename Data>
When a class template uses multiple template parameters, all may be given default values. Like default function arguments, once a default value is used all remaining template parameters must also use their default values. A template type specification list may not start with a comma, nor may it contain multiple consecutive commas.
template <typename Data>
class CirQue;
Default
template arguments may also
be specified when declaring class templates. However, default template
arguments cannot be specified for both the declaration and the definition of a
class template. As a
rule of thumb default template arguments should be
omitted from declarations, as class template declarations are never used
when instantiating objects but are only occasionally used as forward
references. Note that this differs from default parameter value specifications
for member functions in ordinary classes. Such defaults are always specified
when declaring the member functions in the class interface.
In other situations templates are instantiated when they are being used. If
this happens many times (i.e., in many different source files) then this may
slow down the compilation process considerably. The C++0x standard allows
programmers to prevent templates
from being instantiated. For this the C++0x standard introduces the
extern template syntax. Example:
extern template class std::vector<int>;
Having declared the class template it can be used in its translation
unit. E.g., the following function will properly compile:
#include <vector>
#include <iostream>
using namespace std;
extern template class vector<int>;
void vectorUser()
{
vector<int> vi;
cout << vi.size() << '\n';
}
But be careful:
vector header file still needs to be included to make the
features of the class vector known to the compiler. But due to the extern
template declaration none of the used members will be instantiated for the
current compilation unit;
extern template declaration. This not only holds true for
explicitly used members but hidden members (copy constructors, move
constructors, conversion operators, constructors called during promotions, to
name a few): all are assumed by the compiler to have been instantiated
elsewhere;
In a stand-alone program one might postpone defining the required members
and wait for the linker to complain about unresolved external
references. These may then be used to create a series of instantiation
declarations which are then linked to the program to satisfy the linker. Not
a very simple task, though, as the declarations must strictly match the way
the members are declared in the class interface. An easier approach is to
define an
instantiation source file in which all facilities that
are used by the program are actually instantiated in a function that is never
called by the program. By adding this
instantiation function to the source
file containing main we can be sure that all required members will be
instantiated as well. Here is an example of how this can be done:
#include <vector>
#include <iostream>
extern void vectorUser();
int main()
{
vectorUser();
}
// this part is never called. It is added to make sure all required
// features of declared templates will also be instantiated.
namespace
{
void instantiator()
{
std::vector<int> vi;
vi.size();
}
}
Class templates may also define non-type parameters. Like the function template non-type parameters they must be (integral) constants whose values must be known at object instantiation time.
Different from function template non-type parameters the values of class
template non-type parameters are not deduced by the compiler using arguments
passed to class template members. Assume we modify the class template
CirQue so that it has an additional non-type parameter size_t
Size. Next we use this Size parameter in a new constructor defining an
array parameter of Size elements of type Data. Only showing the
relevant constructors (section 21.1.5 provides the rationale for using
the type Data2);
Now two template type parameters are required, e.g., when specifying the type
of the copy constructor's parameter. The new CirQue class template
becomes (showing only the relevant constructors):
template <typename Data, size_t Size>
class CirQue
{
// ... data members
public:
CirQue(CirQue<Data, Size> &other);
CirQue(Data *data);
CirQue(Data const (&arr)[Size]);
...
};
template <typename Data, size_t Size>
CirQue<Data, Size>::CirQue(Data const (&arr)[Size])
:
d_maxSize(Size),
d_size(0),
d_data(operator new(Size * sizeof(Data))),
d_front(d_data),
d_back(d_data),
{
std::copy(arr, arr + Size, back_inserter(*this));
}
Unfortunately, this setup doesn't satisfy our needs as the values of
template non-type parameters are not deduced by the compiler. When the
compiler is asked to compile the following main function it reports a
mismatch between the required and actual number of template parameters:
int main()
{
int arr[30];
CirQue<int> ap(arr);
}
/*
Error reported by the compiler:
In function `int main()':
error: wrong number of template arguments (1, should be 2)
error: provided for `template<class Data, size_t Size>
class CirQue'
*/
Defining Size as a non-type parameter having a default value doesn't
work either. The compiler always uses the default unless its value is
explicitly specified. Reasoning that Size can be 0 unless we need another
value, we might be tempted to specify size_t Size = 0 in the template's
parameter type list. Doing so we create a mismatch between the default
value 0 and the actual size of the array arr as defined in the above
main function. The compiler, using the default value, reports:
In instantiation of `CirQue<int, 0>':
...
error: creating array with size zero (`0')
So, although class templates may use non-type parameters they must always
be specified like type parameters when an object of that class is
defined. Default values can be specified for those non-type parameters causing
the compiler to use the default when the non-type parameter is left
unspecified.
Default template parameter values (either type or non-type template parameters) may not be specified when defining template member functions. In general: function template definitions (and thus: class template member functions) may not be given default template (non) type arguments. If default template arguments are to be used for class template members, they have to be specified by the class interface.
Similar to non-type parameters of function templates default argument values for non-type class template parameters may only be specified as constants:
const pointer.
short can be used when an int is
called for, a long when a double is called for).
size_t parameter is specified, an int may be used as well.
const modifier, however, may be used as their values never change.
Although our attempts to define a constructor of the class CirQue
accepting an array as its argument has failed so far, we're not yet out of
options. In the next section a method is described that does allowing us
to reach our goal.
In contrast, when function templates are used, the actual template parameters are deduced from the arguments used when calling the function. This opens up an alley leading to the solution of our problem. If the constructor itself is turned into a function template (having its own template header), then the compiler will be able to deduce the non-type parameter's value and there is no need anymore to specify it explicitly using a class template non-type parameter.
Members (functions or nested classes) of class templates that are themselves templates are called member templates.
Member templates are defined as any other template, including its own template header.
When converting our earlier CirQue(Data const (&array)[Size]) constructor
into a member template the class template's Data type parameter can still
be used, but we must provide the member template with a non-type parameter of
its own. Its declaration in the (partially shown) class interface looks like
this:
template <typename Data>
class CirQue
{
public:
template <size_t Size>
explicit CirQue(Data const (&arr)[Size]);
};
Its implementation becomes:
template <typename Data>
template <size_t Size>
CirQue<Data>::CirQue(Data const (&arr)[Size])
:
d_size(0),
d_maxSize(Size),
d_data(static_cast<Data *>(operator new(Size * sizeof(Data)))),
d_front(d_data),
d_back(d_data)
{
std::copy(arr, arr + Size, back_inserter(*this));
}
The implementation uses the STL's copy algorithm and a
back_inserter adapter to insert the array's elements into the
CirQue. To use the back_inserter CirQue must define two (public)
typedefs (cf. section 18.2.1):
typedef Data value_type;
typedef value_type const &const_reference;
Member templates have the following characteristics:
template
headers must be used: the class template's template
header is specified first followed by the member template's template header;
CirQue object of a given data
type. As usual for class templates, the data type must be specified when the
object is constructed. To construct a CirQue object from the array
int array[30] we define:
CirQue<int> object(array);
CirQue,
instantiated for the formal template parameter type Data;
namespace SomeName
{
template <typename Type, ...> // class template definition
class ClassName
{
...
};
template <typename Type, ...> // non-inline member definition(s)
ClassName<Type, ...>::member(...)
{
...
}
} // namespace closes
A potentially occurring problem remains. Assume that in addition to the
above member template a CirQue<Data>::CirQue(Data const *data) has been
defined. Some (here not further elaborated) protocol may be defined allowing
the constructor to determine the number of elements that should be stored in
the CirQue object. When we now define
CirQue<int> object(array);
it is this latter constructor, rather than the member template, that is
used by the compiler. The compiler selects this latter constructor as it is a
more specialized version of a constructor of the class CirQue than the
member template (cf. section 20.9). Problems like these can
be solved by defining the constructor CirQue(Data const *data) into a
member template as well. It could receive a template type parameter
Data2. Data cannot be used as template parameters of a member template
may not shadow
template parameters of
its class. Using Data2 instead of Data takes care of this
subtlety. Here is the declaration of the constructor CirQue(Data2 const *)
as it could appear in the CirQue header file:
template <typename Data>
class CirQue
{
template <typename Data2>
explicit CirQue(Data2 const *data);
}
Here is how the two constructors are selected in code defining two
CirQue objects:
int main()
{
int array[30];
int *iPtr = array;
CirQue<int> ac(array); // calls CirQue(Data const (&arr)[Size])
CirQue<int> acPtr(iPtr); // calls CirQue(Data2 const *)
}
template <typename Type>
class TheClass
{
static int s_objectCounter;
};
There will be one TheClass<Type>::objectCounter for each different
Type specification. The following object definitions result in the
instantiation of just one single static variable, shared among the two
objects:
TheClass<int> theClassOne;
TheClass<int> theClassTwo;
Mentioning static members in interfaces does not mean these members are
actually defined. They are only declared and must be defined
separately. With static members of class templates this is no different. The
definition of static members
is usually provided immediately following (i.e., below) the template class
interface. The static member s_objectCounter will usually be defined as
follows, just below its class interface:
template <typename Type> // definition, following
int TheClass<Type>::s_objectCounter = 0; // the interface
Here s_objectCounter is an int and thus independent of the
template type parameter Type.
In list-like constructions, where a
pointer to objects of the class
itself is required, the template type parameter Type must be used when
defining the static variable. Example:
template <typename Type>
class TheClass
{
static TheClass *s_objectPtr;
};
template <typename Type>
TheClass<Type> *TheClass<Type>::s_objectPtr = 0;
As usual, the definition can be read from the variable name back to the
beginning of the definition: s_objectPtr of the class TheClass<Type>
is a pointer to an object of TheClass<Type>.
When a static variable of a template's type parameter's type is defined,
it should of course not be given the initial value 0. The default constructor
(e.g., Type() is usually more appropriate). Example:
template <typename Type> // s_type's definition
Type TheClass<Type>::s_type = Type();
typename has been used to indicate a template type
parameter. However, it is also used to
disambiguate code inside templates. Consider the following function template:
template <typename Type>
Type function(Type t)
{
Type::Ambiguous *ptr;
return t + *ptr;
}
When this code is processed by the compiler, it will complain with an -at
first sight puzzling- error message like:
4: error: 'ptr' was not declared in this scope
The error message is puzzling as it was the programmer's intention to
declare a pointer to a type Ambiguous defined within the class template
Type. But the compiler, confronted with Type::Ambiguous may interpret
the statement in various ways. Clearly it cannot inspect Type itself
trying to uncover Type's true nature as Type is a template
type. Because of this Type's actual definition isn't available yet.
The compiler is confronted with two possibilities: either
Type::Ambiguous is a static member of the as yet mysterious template
Type, or it is a subtype of Type. As the standard
specifies that the compiler must assume the former, the statement
Type::Ambiguous *ptr;
is interpreted as a multiplication of the static member
Type::Ambiguous and the (now undeclared) entity ptr. The reason for
the error message should now be clear: in this context ptr is unknown.
To disambiguate code in which an identifier refers to a
subtype of a template type parameter the keyword typename must be
used. Accordingly, the above code is altered into:
template <typename Type>
Type function(Type t)
{
typename Type::Ambiguous *ptr;
return t + *ptr;
}
Classes fairly often define subtypes. When such subtypes appear inside
template definitions as subtypes of template type parameters the template
keyword must be used to identify them as subtypes. Example: a class
template Handler defines a typename Container as its template type
parameter. It also defines a data member storing the iterator returned by the
container's begin member. In addition Handler offers a constructor
accepting any container supporting a begin member. Handler's class
interface could then look like this:
template <typename Container>
class Handler
{
Container::const_iterator d_it;
public:
Handler(Container const &container)
:
d_it(container.begin())
{}
};
What did we have in mind when designing this class?
Container represents any container supporting
iterators.
begin. The
initialization d_it(container.begin()) clearly depends on the
template's type parameter, so it's only checked for basic syntactic
correctness.
const_iterator, defined in the class Container.
typename is required. If
this is omitted and a Handler is instantiated the compiler produces a
peculiar compilation error:
#include "handler.h"
#include <vector>
using namespace std;
int main()
{
vector<int> vi;
Handler<vector<int> > ph(vi);
}
/*
Reported error:
handler.h:4: error: syntax error before `;' token
*/
Clearly the line
Container::const_iterator d_it;
in the class Handler causes a problem. It is interpreted by the
compiler as a static member instead of a subtype. The problem is
solved using typename:
template <typename Container>
class Handler
{
typename Container::const_iterator d_it;
...
};
An interesting illustration that the compiler indeed assumes X::a to
be a member a of the class X is provided by the error message we get
when we try to compile main using the following implementation of
Handler's constructor:
Handler(Container const &container)
:
d_it(container.begin())
{
size_t x = Container::ios_end;
}
/*
Reported error:
error: `ios_end' is not a member of type `std::vector<int,
std::allocator<int> >'
*/
Now consider what happens if the function template introduced at the
beginning of this section doesn't return a Type value, but a
Type::Ambiguous value. Again, a subtype of a template type is referred to,
and typename must be used:
template <typename Type>
typename Type::Ambiguous function(Type t)
{
return t.ambiguous();
}
Using typename in the specification of a return type is further
discussed in section 22.1.1.
Typenames can be embedded in typedefs. As is often the case, this
reduces the complexities of declarations and definitions appearing
elsewhere. In the next example the type Iterator is defined as a subtype
of the template type Container. Iterator may now be used without
requiring the use of the keyword typedef:
template <typename Container>
class Handler
{
typedef typename Container::const_iterator Iterator;
Iterator d_it;
...
};
CirQue can be used for many different types. Their common
characteristic is that they can simply be pointed at by class's d_data
member. But this is not always as simple as it looks. What if Data turns
out to be a vector<int>? For such data types the vanilla CirQue
implementation will not properly work and a specialization could be
considered. When considering a specialization one should also consider
inheritance. Often a class derived from the class template accepting the
incompatible data structure as its argument but otherwise equal to the
original class template can easily be designed. The developmental advantage of
inheritance over specialization is clear: the inherited class inherits the
members of its base class while the specialization inherits nothing. All
members defined by the original class template must be implemented again by
the class template's specialization.
The specialization considered here is a true specialization in that the
data members and representation used by the specialization greatly differ from
the original CirQue class template. Therefore all members defined by the
orginal class template must be modified to fit the specialization's data
organization.
Like function template specializations class template specializations
start with a template header that may or may not have an empty template
parameter list. If the template parameters are directly specialized by the
specialization it remains empty (e.g., CirQue's template type parameter
Data is specialized for char * data). But the template parameter list
may show typename Data when specializing for a vector<Data>, i.e., a
vector storing any type of data. This leads to the following principle:
A template specialization is recognized by the template argument list following a function or class template's name and not by an empty template parameter list. Class template specializations may have non-empty template parameter lists. If so, a partial class template specialization is defined.
A completely specialized class has the following characteristics:
template <> header, but must immediately start with the member function's
header.
Here is an example of a completely specialized CirQue class, specialized
for a vector<int>. All members of the specialized class are declared, but
only non-trivial implementations of its members are provided. The specialized
class uses a copy of the vector passed to the constructor and implements a
circular queue using its vector data member:
#ifndef INCLUDED_CIRQUEVECTOR_H_
#define INCLUDED_CIRQUEVECTOR_H_
#include <vector>
#include "cirque.h"
template<>
class CirQue<std::vector<int>>
{
typedef std::vector<int> IntVect;
IntVect d_data;
size_t d_size;
typedef IntVect::iterator iterator;
iterator d_front;
iterator d_back;
public:
typedef int value_type;
typedef value_type const &const_reference;
enum Exception
{
EMPTY,
FULL
};
CirQue();
CirQue(IntVect const &iv);
CirQue(CirQue<IntVect> const &other);
CirQue &operator=(CirQue<IntVect> const &other);
int &back();
int &front();
bool empty() const;
bool full() const;
size_t maxSize() const;
size_t size() const;
void pop_front();
void push_back(int const &object);
void swap(CirQue<IntVect> &other);
private:
iterator inc(iterator const &iter);
};
CirQue<std::vector<int>>::CirQue()
:
d_size(0)
{}
CirQue<std::vector<int>>::CirQue(IntVect const &iv)
:
d_data(iv),
d_size(iv.size()),
d_front(d_data.begin()),
d_back(d_data.begin())
{}
CirQue<std::vector<int>>::CirQue(CirQue<IntVect> const &other)
:
d_data(other.d_data),
d_size(other.d_size),
d_front(d_data.begin() + (other.d_front - other.d_data.begin())),
d_back(d_data.begin() + (other.d_back - other.d_data.begin()))
{}
CirQue<std::vector<int>> &CirQue<std::vector<int>>::operator=(
CirQue<IntVect> const &rhs)
{
CirQue<IntVect> tmp(rhs);
swap(tmp);
}
void CirQue<std::vector<int>>::swap(CirQue<IntVect> &other)
{
char tmp[sizeof(CirQue<IntVect>)];
memcpy(tmp, &other, sizeof(CirQue<IntVect>));
memcpy(&other, this, sizeof(CirQue<IntVect>));
memcpy(this, tmp, sizeof(CirQue<IntVect>));
}
void CirQue<std::vector<int>>::pop_front()
{
if (d_size == 0)
throw EMPTY;
d_front = inc(d_front);
--d_size;
}
void CirQue<std::vector<int>>::push_back(int const &object)
{
if (d_size == d_data.size())
throw FULL;
*d_back = object;
d_back = inc(d_back);
++d_size;
}
inline int &CirQue<std::vector<int>>::back()
{
return d_back == d_data.begin() ? d_data.back() : d_back[-1];
}
inline int &CirQue<std::vector<int>>::front()
{
return *d_front;
}
CirQue<std::vector<int>>::iterator CirQue<std::vector<int>>::inc(
CirQue<std::vector<int>>::iterator const &iter)
{
iterator tmp(iter + 1);
tmp = tmp == d_data.end() ? d_data.begin() : tmp;
return tmp;
}
#endif
Here is an example showing the use of the specialized CirQue class:
static int iv[] = {1, 2, 3, 4, 5};
int main()
{
vector<int> vi(iv, iv + 5);
CirQue<vector<int>> ci(vi);
cout << ci.size() << ' ' << ci.front() << ' ' << ci.back() << '\n';
ci.pop_front();
ci.pop_front();
CirQue<vector<int>> cp;
cp = ci;
cout << cp.size() << ' ' << cp.front() << ' ' << cp.back() << '\n';
cp.push_back(6);
cout << cp.size() << ' ' << cp.front() << ' ' << cp.back() << '\n';
}
With partial specializations a subset (any subset) of template type parameters are given specific values. It is also possible to use a class template partial specialization when the intent is to specialize the class template, but to parameterize the data type that is processed by the specialization.
To start our discussion with an example of the latter use of a partial class
template specialization consider the class CirQue<vector<int>> developed
in the previous section. When designing CirQue<vector<int>> you may have
asked yourself how many specializations you'd have to implement. One for
vector<int>, one for vector<string>, one for vector<double>? As
long as the data types handled by the vector used by the class
CirQue<vector<...>> behaves like an int (i.e., is a value-type of
class) the answer is: zero. Instead of defining full specializations for each
new data type the data type itself can be parameterized, resulting in a
partial specialization:
template <typename Data>
class CirQue<std::vector<Data>>
{
...
};
The above class is a specialization as a template argument list is
appended to the CirQue class name. But as the class template itself has a
non-empty template parameter list it is in fact recognized as a partial
specialization. There is one characteristic that distinguishes the
implementation (subsequent to the class template's interface) of a class
template member function of a partial specialization from the implementation
of a member function of a full specialization. Implementations of partially
specialized class template member functions receives a template header. No
template headers are used when implementing fully specialized class template
members.
Implementing the partial specialization for CirQue is not difficult and is
left as an exercise for the reader (hints: simply change int into Data
in the CirQue<vector<int>> specialization of the previous section).
Remember to prefix the type iterator by typename (as in typedef
typename DataVect::iterator iterator) (as discussed in section
21.2.1).
The remainder of this section concentrates on specializing class template non-type template parameters. These partial specializations will be illustrated using some simple concepts defined in matrix algebra, a branch of linear algebra.
A matrix is commonly thought of as a table of some rows and columns,
filled with numbers. Immediately we recognize an opening for using templates:
the numbers might be plain double values, but they could also be complex
numbers, for which our complex container (cf. section 12.4) might
prove useful. Our class template is therefore provided with a DataType
template type parameter. It is specified when a
matrix is constructed. Some simple matrices using double values, are:
1 0 0 An identity matrix,
0 1 0 (a 3 x 3 matrix).
0 0 1
1.2 0 0 0 A rectangular matrix,
0.5 3.5 18 23 (a 2 x 4 matrix).
1 2 4 8 A matrix of one row
(a 1 x 4 matrix), also known as a
`row vector' of 4 elements.
(column vectors are analogously defined)
Since matrices consist of well defined numbers of rows and columns (the
dimensions of the matrix), that normally do not change when matrices are
used, we might consider specifying their values as template non-type
parameters. Since the DataType = double selection will be used in the
majority of cases, double can be selected as the template's default type
argument. Since it's a sensible default, the DataType template type
parameter is used last in the template type parameter list.
Our template class Matrix begins its life as:
template <size_t Rows, size_t Columns, typename DataType = double>
class Matrix
...
Various operations are defined on matrices. They may, for example be
added, subtracted or multiplied. Here we will not focus on these
operations. Rather, we concentrate on some simple operations: computing
marginals and sums:
Rows) sums in corresponding
elements of a column vector of Rows elements.
Columns) sums in corresponding elements of a row vector of
Columns elements.
matrix: row
marginals:
1 2 3 6
4 5 6 15
column 5 7 9 21 (sum)
marginals
What do we want our class template to offer?
Rows' rows each containing `Columns' elements of type
DataType. It can be an array, rather than a pointer, since the matrix'
dimensions are known a priori. Since a vector of Columns elements (a
row of the matrix), as well as a vector of Row elements (a column
of the matrix) is often used, the class could use typedefs to represent
them. The class interface's initial section thus contains:
typedef Matrix<1, Columns, DataType> MatrixRow;
typedef Matrix<Rows, 1, DataType> MatrixColumn;
MatrixRow d_matrix[Rows];
template <size_t Rows, size_t Columns, typename DataType>
Matrix<Rows, Columns, DataType>::Matrix()
{
std::fill(d_matrix, d_matrix + Rows, MatrixRow());
}
template <size_t Rows, size_t Columns, typename DataType>
Matrix<Rows, Columns, DataType>::Matrix(std::istream &str)
{
for (size_t row = 0; row < Rows; row++)
for (size_t col = 0; col < Columns; col++)
str >> d_matrix[row][col];
}
operator[] member (and its const variant) only
handles the first index, returning a reference to a complete
MatrixRow. How elements in a MatrixRow can be retrieved is shortly
covered. To keep the example simple, no array bound check has been
implemented:
template <size_t Rows, size_t Columns, typename DataType>
Matrix<1, Columns, DataType>
&Matrix<Rows, Columns, DataType>::operator[](size_t idx)
{
return d_matrix[idx];
}
Matrix. Marginals are vectors (either a
MatrixRow, containing the column marginals; a MatrixColumn, containing
the row marginals; or a single value) computed as the sum of a vector of
marginals or as the value of a 1 x 1 matrix initialized from a generic
Matrix. As marginals can be considered special forms of matrices we can
now construct partial specializations to handle MatrixRow and
MatrixColumn objects, and a partial specialization handling 1 x 1
matrices. The required specializations are shortly defined. They are used
here to compute marginals and the sum of all elements in a matrix. Here are
the implementations of the members computing the marginals and the sum of all
elements of a matrix:
template <size_t Rows, size_t Columns, typename DataType>
Matrix<1, Columns, DataType>
Matrix<Rows, Columns, DataType>::columnMarginals() const
{
return MatrixRow(*this);
}
template <size_t Rows, size_t Columns, typename DataType>
Matrix<Rows, 1, DataType>
Matrix<Rows, Columns, DataType>::rowMarginals() const
{
return MatrixColumn(*this);
}
template <size_t Rows, size_t Columns, typename DataType>
DataType Matrix<Rows, Columns, DataType>::sum() const
{
return rowMarginals().sum();
}
Class template partial specializations can be defined for any (subset)
of template parameters. They can be defined for template type parameters
and for template non-type parameters alike. Our first partial specialization
defines a row of a generic Matrix, mainly (but not only) used for the
construction of column marginals. Here is how such a partial specialization is
designed:
DataType = double) since defaults were already specified by the generic
class template definition. The specialization must follow the definition
of the generic class template's definition, or the compiler complains that it
doesn't know what class is being specialized. Following the template header,
the class's interface starts. It's a class template (partial) specialization
so the class name must be followed by a template argument list specifying the
template arguments used by the partial specialization. The arguments specify
explicit types or values for some of the template's parameters. Remaining
types are simply copied from the class template partial specialization's
template parameter list. E.g., the MatrixRow specialization specifies 1
for the generic class template's Rows type parameter (as we're talking
here about a single row). Both Columns and DataType remain to be
specified. The MatrixRow partial specialization therefore starts as
follows:
template <size_t Columns, typename DataType> // no default allowed
class Matrix<1, Columns, DataType>
MatrixRow holds the data of a single row. So it needs a
data member storing Columns values of type DataType. Since Columns
is a constant value, the d_row data member can be defined as an array:
DataType d_column[Columns];
MatrixRow's data elements using DataType's default constructor:
template <size_t Columns, typename DataType>
Matrix<1, Columns, DataType>::Matrix()
{
std::fill(d_column, d_column + Columns, DataType());
}
Another constructor is needed initializing a MatrixRow object
with the column marginals of a generic Matrix object. This requires us to
provide the constructor with a non-specialized Matrix parameter.
The
rule of thumb here is to define a member template that allows us to
keep the general nature of the parameter. Since the generic Matrix
template requires three template parameters. Two of these were already
provided by the template specialization. The third parameter is mentioned in
the member template's template header. Since this parameter refers to the
number of rows of the generic matrix it is simply called Rows.
Here then is the implementation of the second constructor, initializing the
MatrixRow's data with the column marginals of a generic Matrix object:
template <size_t Columns, typename DataType>
template <size_t Rows>
Matrix<1, Columns, DataType>::Matrix(
Matrix<Rows, Columns, DataType> const &matrix)
{
std::fill(d_column, d_column + Columns, DataType());
for (size_t col = 0; col < Columns; col++)
for (size_t row = 0; row < Rows; row++)
d_column[col] += matrix[row][col];
}
The constructor's parameter is a reference to a Matrix template using
the additional Row template parameter as well as the template parameters
of the partial specialization.
MatrixRow an overloaded
operator[]() is of course useful. Again, the const variant can be
implemented like the non-const variant. Here is its implementation:
template <size_t Columns, typename DataType>
DataType &Matrix<1, Columns, DataType>::operator[](size_t idx)
{
return d_column[idx];
}
Matrix class and the
partial specialization defining a single row the compiler will select the
row's specialization whenever a Matrix is defined using Row = 1. For
example:
Matrix<4, 6> matrix; // generic Matrix template is used
Matrix<1, 6> row; // partial specialization is used
The partial specialization for a MatrixColumn is constructed
similarly. Let's present its highlights (the full Matrix class template
definition as well as all its specializations are provided in the
cplusplus.yo.zip archive (at SourceForge) in the
file yo/classtemplates/examples/matrix.h):
template <size_t Rows, typename DataType>
class Matrix<Rows, 1, DataType>
MatrixRow constructors were implemented. Their implementations are
left as an exercise to the reader (and they can be found in matrix.h).
sum is defined to compute the sum of the
elements of a MatrixColumn vector. It's simply implemented
using the accumulate generic algorithm:
template <size_t Rows, typename DataType>
DataType Matrix<Rows, 1, DataType>::sum()
{
return std::accumulate(d_row, d_row + Rows, DataType());
}
The reader might wonder what happens if we specify the following matrix:
Matrix<1, 1> cell;
Is this a MatrixRow or a MatrixColumn specialization? The answer
is: neither. It's ambiguous, precisely because both the columns and
the rows could be used with a (different) template partial specialization. If
such a Matrix is actually required, yet another specialized template must
be designed. Since this template specialization can be useful to
obtain the sum of the elements of a Matrix, it's covered here as well.
DataType. The class definition specifies
two fixed values: 1 for the number of rows and 1 for the number of columns:
template <typename DataType>
class Matrix<1, 1, DataType>
Matrix type are again implemented as
member templates. For example:
template <typename DataType>
template <size_t Rows, size_t Columns>
Matrix<1, 1, DataType>::Matrix(
Matrix<Rows, Columns, DataType> const &matrix)
:
d_cell(matrix.rowMarginals().sum())
{}
template <typename DataType>
template <size_t Rows>
Matrix<1, 1, DataType>::Matrix(Matrix<Rows, 1, DataType> const &matrix)
:
d_cell(matrix.sum())
{}
Matrix<1, 1> is basically a wrapper around a DataType
value, we need members to access that latter value. A type conversion
operator might be usefull, but we also defined a get member to obtain
the value if the conversion operator isn't used by the compiler (which
happens when the compiler is given a choice, see section
10.3). Here are the accessors (leaving out their const
variants):
template <typename DataType>
Matrix<1, 1, DataType>::operator DataType &()
{
return d_cell;
}
template <typename DataType>
DataType &Matrix<1, 1, DataType>::get()
{
return d_cell;
}
main function shows how the Matrix class template
and its partial specializations can be used:
#include <iostream>
#include "matrix.h"
using namespace std;
int main(int argc, char **argv)
{
Matrix<3, 2> matrix(cin);
Matrix<1, 2> colMargins(matrix);
cout << "Column marginals:\n";
cout << colMargins[0] << " " << colMargins[1] << '\n';
Matrix<3, 1> rowMargins(matrix);
cout << "Row marginals:\n";
for (size_t idx = 0; idx < 3; idx++)
cout << rowMargins[idx] << '\n';
cout << "Sum total: " << Matrix<1, 1>(matrix) << '\n';
return 0;
}
/*
Generated output from input: 1 2 3 4 5 6
Column marginals:
9 12
Row marginals:
3
7
11
Sum total: 21
*/
Variadic templates are defined for function templates and for class templates. Variadic templates allow us to specify an arbitrary number of template arguments of any type.
Variadic templates were added to the language to prevent us from having to define many overloaded templates and to be able to create type safe variadic functions.
Although C (and C++) support variadic functions, their use has always been deprecated in C++ as those functions are notoriously type-unsafe. Variadic function templates can be used to process objects that until now couldn't be processed properly by C-style variadic functions.
Template header of variadic templates show the phrase
typename ... Params
in their template headers (Params being a formal name). A
variadic class template Variadic could be declared as follows:
template<typename ... Params> class Variadic;
Assuming the class template's definition is available then this template
can be instantiated using any number of template arguments. Example:
class Variadic<
int,
std::vector<int>,
std::map<std::string, std::vector<int>>
> v1;
The template argument list of a variadic template can be
empty. Example:
class Variadic<> empty;
If this is considered undesirable an empty template argument list can be
prevented by providing one or more fixed parameters. Example:
template<typename First, typename ... Rest>
class tuple;
C's function
printf is a well-known example of a type-unsafe
function. It is turned into a type-safe function when it is implemented as a
variadic function template. Not only does this turn the function into a
type-safe function but it is also automatically extended to accept any type
that can be defined by C++. Here is a possible declaration of a variadic
function template printcpp:
template<typename ... Params>
void printcpp(std::string const &strFormat, Params ... parameters);
The
ellipsis (
...) used in the declaration serves two purposes:
sizeof operator:
template<typename ... Params>
struct StructName
{
static size_t const s_size = sizeof ... (Params);
};
// StructName<int, char>::s_size -- initialized to 2
By defining an partial specialization of a variadic template explicitly
defining an additional template type parameter we can associate the first
template argument of a parameter pack with the additional tyoe
parameter. The setup of such a variadic function template (e.g., printcpp,
see the previous section) is as follows:
printcpp function receives at least a format
string. Following the format string any number of additional arguments may be
specified.
First. The types of any remaining
arguments are bound to the template function's second template parameter,
which is a parameter pack.
First parameter. As
reduces the size of the recursive call's parameter pack the recursion will
eventually stop.
The overloaded non-template function prints the remainder of the format string, en passant checking for any left-over format specifications:
void printcpp(string const &format)
{
size_t left = 0;
size_t right = 0;
while (true)
{
if ((right = format.find('%', right)) == string::npos)
break;
if (format.find("%%", right) != right)
throw std::runtime_error(
"printcpp: missing arguments");
++right;
cout << format.substr(left, right - left);
left = ++right;
}
cout << format.substr(left);
}
Here is the variadic function template's implementation:
template<typename First, typename ... Params>
void printcpp(std::string const &format, First value, Params ... params)
{
size_t left = 0;
size_t right = 0;
while (true)
{
if ((right = format.find('%', right)) == string::npos) // 1
throw std::logic_error("printcpp: too many arguments");
if (format.find("%%", right) != right) // 2
break;
++right;
cout << format.substr(left, right - left);
left = ++right;
}
cout << format.substr(left, right - left) << value;
printcpp(format.substr(right + 1), params ...);
}
%. If none is found then the function is called with too many arguments
and it throws an exception;
%%. If only a single
% has been seen the while-loop ends, the format string is inserted
into cout up to the % followed by value, and the recursive call
receives the remaing part of the format string as well as the remaining
parameter pack;
%% was seen the format string is inserted up to the second
%, which is ignored, and processing of the format string continues beyond
the %%.
printcpp when compiling the function
template.
Different from C's printf function printcpp only recognizes
% and %% as format specifiers. The above implementation does not
recognize, e.g., field widths. Type specifiers like %c and %x are of
course not needed as ostream's insertion operator is aware of the types of
the arguments that are inserted into the ostream. Extending the format
specifiers so that field widths etc. are recognized by this printcpp
implementation is left as an exercise to the reader. Here is an example
showing how printcpp can be called:
printcpp("Hello % with %%main%% called with % args"
" and a string showing %\n",
"world", argc, string("A String"));
string's member insert. String::insert has several
overloaded implementations. It can be used to insert text (completely or
partially) provided in a string or in a char const * argument; to
insert single characters a specified number of times; iterators can be used to
specify the range of characters to be inserted; etc., etc.. All in,
string offers five overloaded insert members.
Assume the existence of a class Inserter that is used to insert
information into all kinds of objects. Such a class could have a string
data member into which information can be inserted. Inserter's interface
only partially has to copy string's interface to realize this: only
string::insert's interfaces must be duplicated. These duplicating
interfaces often contain one statement (calling the appropriate member
function of the object's data member) and are for this reason often
implemented in-line. These wrapper functions merely
forward their
parameters to the appropriate member function of the object's data member.
Factory functions also frequently forward their parameters to the constructors of objects that they return.
Before the C++0x standard the interfaces of overloaded functions needed to
be duplicated by the forwarding entity: Inserter needed to duplicate the
interfaces of all five string::insert members; a factory function needed
to duplicate the interfaces of the constructors of the class of the objects it
returned.
The C++0x standard simplifies and generalizes forwarding of parameters by offering perfect forwarding, implemented through rvalue references and variadic templates. With perfect forwarding the arguments passed to functions are `perfectly forwarded' to nested functions. Forwarding is called perfect as the arguments are forwarded in a type-safe way. To use perfect forwarding nested functions must define parameter lists matching the forwarding parameters both in types and number.
Perfect forwarding is easily implemented:
Params && ... params);
std::forward is used to forward the forwarding function's
arguments to the nested function, keeping track of their types and
number. Before using forward the
<utility> header file must
have been included. The nested function is then called using this
stanza for its arguments: std::forward<Params>(params) ....
In the following example perfect forwarding is used to implement
Inserter::insert. The insert function that's actually called now
depends on the types and number of arguments that are passed to
Inserter::insert:
class Inserter
{
std::string d_str; // somehow initialized
public:
// constructors not implemented,
// but see below
Inserter();
Inserter(std::string const &str);
Inserter(Inserter const &other);
Inserter(Inserter const &&other);
template<typename ... Params>
void insert(Params && ... params)
{
d_str.insert(std::forward<Params>(params) ...);
}
};
A factory function returning a Inserter can also easily be implemented
using perfect forwarding. Rather than defining four overloaded factory
functions a single one now suffices. By providing the factory function with an
additional template type parameter specifying the class of the object to
construct the factory function is turned into a completely general factory
function:
template <typename Class, typename ... Params>
Class factory(Params && ... params)
{
return Class(std::forward<Params>(params) ...);
}
Here are some examples showing its use:
Inserter inserter(factory<Inserter>("hello"));
string delimiter(factory<string>(10, '='));
Inserter copy(factory<Inserter>(inserter));
The function std::forward is provided by the standard library. It
performs no magic, but merely returns params as an nameless object. That
way it acts like std::move that also removes the name from an object,
returning it as a nameless object. The unpack operator has nothing to do with
the use of forward but merely tells the compiler to apply forward to
each of the arguments in turn. Thus it behaves similarly to C's ellipsis
operator used by variadic functions.
Perfect forwarding was introduced in section 20.3.5: a template
function defining a Type &¶m, with Type being a template type
parameter will convert Type && to Tp & if the function is called with
an argument of type Tp &. Otherwise it will bind Type to Tp,
with param being defined as Tp &¶m. As a result an lvalue
argument will bind to an lvalue-type (Tp &), while an rvalue argument
will bind to an rvalue-type (Tp &&).
The function std::forward merely passes the argument (and its type) on to
the called function or object. Here is its simplified implementation:
typedef <type T>
T &&forward(T &&a)
{
return a;
}
Since T && turns into an lvalue reference when forward is called
with an lvalue (or lvalue reference) and remains an rvalue reference if
forward is called with an rvalue reference and since forward (like
std::move) anonymizes the variable passed as argument to forward the
argument value is forwarded while keeping its type
from the function's parameter to the called function's argument. This is
called perfect forwarding as the called function will only be called if
the types of the arguments used when calling the `outer' function (e.g.,
factory) exactly match the types of the parameters of the called function
(e.g., Class's constructor). Perfect forwarding therefore is a tool to
uphold type safety.
A cosmetic improvement to forward forces users of forward to
specify the type to use rather than to have the compiler deduct the type as a
result of the function template parameter type deduction's process. This is
realized by a small support struct template:
template <typename T>
struct identity
{
typedef T type;
};
This struct merely defines identity::type as T, but as it is a
struct it must be specified explicitly. It cannot be determined from the
function's argument itself. The subtle modification to the above
implementation of forward thus becomes (cf. section 21.2.1 for
an explanation of the use of typename):
typedef <type T>
T &&forward(typename identity<T>::type&& a)
{
return a;
}
Now forward must explicitly state the a's plain type, as in:
std::forward<Params>(params)
Using the std::forward function and the rvalue reference specification
is not restricted to the context of parameter packs. Because of the special
way rvalue references to template type parameters are treated (cf. section
20.3.5) they can profitably be used to forward individual function
parameters as well. Here is an example showing how an argument to a function
can be forwarded from a template to a function that is itself passed to the
template as a pointer to a (unspecified) function:
template<typename Fun, typename ArgType>
void caller(Fun fun, ArgType &&arg)
{
fun(std::forward<ArgType>(arg));
}
A function display(ostream &out) and increment(int &x) may now
both be called through caller. Example:
caller(display, cout);
int x = 0;
caller(increment, x);
const
and non-const references. Here is an example of a program that won't
compile:
template <typename T>
class Wrap
{
public:
Wrap(T const &tr);
Wrap(T &tr);
};
int main()
{
int i;
Wrap<int &> wrap(i);
}
with the compiler generating an error message like
'Wrap<T>::Wrap(T&) [with T = int&]' cannot be overloaded
with 'Wrap<T>::Wrap(const T&) [with T = int&]'
Earlier, in section 19.2.2 a program was shown using a support function having signature
int stringcasecmp(string lhs, string rhs)
where it was noted that this function did introduce inefficiency, but that
an improved signature, using string const & parameters failed to compile,
with std::bind2nd (cf. section 18.1.4) generating an error like the
one mentioned here. The following sequence of templates were instantiated in
that example:
ptr_fun was instantiated, receiving stringcasecmp. The
function template ptr_fun returns a pointer_to_binary_function, for
which its first_arugment_type and second_argument_type are deducted as
string const & if stringcasecmp defines string const &
parameters;
pointer_to_binary_function object is passed to
binary2nd. As you may have guessed, we're already on dangerous grounds as
pointer_to_binary_function is a class template defining reference template
type parameters;
bind2nd function template merely creates and returns a
binder2nd object, initializing it with the pointer_to_binary_function
object and the type of the second parameter of stringcasecmp (which can be
ignoredin the current discussion);
binder2nd class template object is a functor defining two
overloaded function operators (using declarations and simplified type names to
ease focussing on the essence):
result_type operator()(first_argument_type const &x) const;
result_type operator()(first_argument_type &x) const;
As first_argument_type is a template type parameter deducted to be
string const & we have overloaded functions using reference parameters and
a template type parameter which itself is a reference parameter. As a
consequence the `cannot be overloaded' error message results and
stringcasecmp cannot be defined as a function having string const &
parameters.
This problem could not elegantly be solved in before the arrival of the C++0x
standard, as C++ did not have perfect forwarding before the C++0x
standard. The solution requires a reformulation of bind2nd and
binder2nd exploiting the
features offered by perfect forwarding. Other adaptors defined by the STL
may require comparable reformulations. The modifications required for
bind2nd and binder2nd are covered here; it's up to the reader to
implement such modifications for other adaptors when appropriate.
std::bind2nd is rewritten here as Bind2nd. It's actually a copy of
bind2nd, but this time creating Binder2nd rather than
binder2nd. As the (non-configurable) venom is in bind2nd's
binder2nd call an alternative for bind2nd is needed to avoid calling
binder2nd. Alternative approaches are also possible. A class template
specialization for binder2nd could be developed expecting a particular
class which is a wrapper around pointer_to_binary_function. It wouldn't
make much of a difference, as in that case ptr_fun would have to be
rewritten as well.
So we stick to Bind2nd having the following obvious implementation (all
functions defined in-class to save some space):
template<typename Operation, typename SecondArg>
inline Binder2nd<Operation> Bind2nd(Operation const &operation,
SecondArg const &arg2)
{
return Binder2nd<Operation>(
operation,
typename Operation::second_argument_type(arg2)
);
}
The meat of the solution is of course in Binder2nd, now using perfect
forwarding. There is now only one function operator member (operator())
which is now defined as a function template, using the perfect forwarding
incantation. Its body forwards the arguments that were actually passed to the
function call operator as well as Binder2nd's (fixed) second argument
to the `operation', which is stringcasecmp. Please note that the forwarded
arguments can very well be followed by additional arguments (here:
d_arg2). In Binder2nd's implementation there's no guarantee that
there will actually only be one perfectly forwarded argument. Although such a
guarantee could easily be built-in (cf. section 22.6) it really isn't
required as the compiler will detect whether there are indeed two arguments
bing passed to stringcasecmp, generating an error message if not. Here is
Binder2nd's implementation:
template<typename Operation>
class Binder2nd:
public std::unary_function<typename Operation::first_argument_type,
typename Operation::result_type>
{
typedef typename Operation::second_argument_type SecondArg;
protected:
Operation d_operation;
SecondArg d_arg2;
public:
Binder2nd(Operation const &operation, SecondArg const &arg2)
:
d_operation(operation),
d_arg2(arg2)
{}
template <typename ... Params>
typename Operation::result_type
operator()(Params && ... params) const
{
return d_operation(std::forward<Params>(params) ..., d_arg2);
}
};
The program shown in section 19.2.2 remains unaltered but for
the use of bind2nd, which now becomes Bind2nd:
...
auto pos = find_if(
v1.begin(), v1.end(),
not1( Bind2nd(ptr_fun(stringicmp), target) )
);
The two programs produce identical output, but the program developed here
is a lot more efficient than the one developed originally, in section
19.2.2.
The unpack operator can also be used to define template classes that are derived from any number of base classes. Here is how it's done:
template <typename... BaseClasses>
class Combi: public BaseClasses ... // derive from base classes
{
public:
// specify base class objects
// to its constructor using
// perfect forwarding
Combi(BaseClasses && ... baseClasses)
:
BaseClasses(baseClasses) ... // use base class initializers
{} // for each of the base
}; // classes
This allows us to define classes that combine the features of any number
of other classes. If the class Combi is derived of classes A, B, and
C then Combi is-a A, B, and C. It should of course be given a
virtual destructor. A Combi object can be passed to functions expecting
pointers or references to any of its base class type objects. Here is an
example defining Combi as a class derived from a vector of complex
numbers, a string and a pair of ints and doubles (using uniform intializers in
a sequence matching the sequence of the types specified for the used Combi
type):
typedef Combi<vector<double>>, string, pair<int, double>> MultiTypes;
MultiTypes mt = {{3.5, 4}, "mt", {1950, 1.72}};
The same construction can also be used to define template data members
that support variadic type lists such as tuples (cf. section ref
TUPLES). Such a class could be designed along these lines:
template <typename ... Types>
struct Multi
{
std::tuple<Types ...> d_tup; // define tuple for Types types
Multi(Types ... types)
: // initialize d_tup from Multi's
d_tup(std::forward<Types>(types) ...) // arguments
{}
};
The ellipses that are used when forwarding parameter packs are
essential. The compiler considers their omission an error. In the following
struct definition it was the intent of the programmer to pass a parameter
pack on to a nested object construction but ellipses were omitted while
specifying the template parameters, resulting in a
parameter packs not expanded with `...' error message:
template <int size, typename ... List>
struct Call
{
Call(List && ... list)
{
Call<size - 1, List &&> call(std::forward<List>(list) ...);
}
};
Instead of the above definition of the call object the programmer
should have used:
Call<size - 1, List && ...> call(std::forward<List>(list) ...);
operator()) of such classes
themselves have arguments then the types of those arguments may also be
abstracted by defining the function call operator itself as a member template.
Example:
template <typename Class>
class Filter
{
Class obj;
public:
template <typename Param>
Param operator()(Param const ¶m) const
{
return obj(param);
}
};
The template class Filter is a wrapper around Class, filtering
Class's function call operator through its own function call operator. In
the above example the return value of Class's function call operator is
simply passed on, but any other manipulation is of course also possible.
A type that is specified as Filter's template type argument may of
course have multiple function call operators:
struct Math
{
int operator()(int x);
double operator()(double x);
};
Math objects can now be filtered using Filter<Math> fm using
Math's first or second function call operator, depending on the actual
argument type. With fm(5) the int-version is used, with fm(12.5)
the double-version is used.
Unfortunately this scheme doesn't work if the function call operators have
different return and argument types. Because of this the following class
Convert cannot be used with Filter:
struct Convert
{
double operator()(int x); // int-to-double
std::string operator()(double x); // double-to-string
};
This problem can be tackled successfully by the class template
std::result_of<Functor(Typelist)> that is defined by the
C++0x standard. Before using std::result_of the header file
<functional> must have been included.
The result_of class template offers a tyedef, type, representing
the type that is returned by Functor<TypeList>. It can be used to improve
Filter as follows:
template <typename Class>
class Filter
{
Class obj;
public:
template <typename Arg>
typename std::result_of<Class(Arg)>::type
operator()(Arg const &arg) const
{
return obj(arg);
}
};
Using this definition, Filter<Convert> fc can be constructed and
fc(5) will return a double, while fc(4.5) returns a
std::string.
The class Convert must define the relationships between its function call
operators and their return types. Predefined function objects (like those in
the standard template libary) already do so, but self-defined function objects
must to this explicitly.
If a function object class defines only one function call operator it can
define its return type by a typedef. If the above class Convert would
only define the first of its two function call operators then the typedef
(in the class's public section) should be:
typedef double type;
If multiple function call operators are defined, each with its own signature
and return type, then the association between signature and return type is set
up as follows (all in the class's public section):
struct result like this:
template <typename Signature>
struct result;
struct result. Convert's first function call operator gives rise to:
template <typename Class>
struct result<Class(int)>
{
typedef double type;
};
and Convert's second function call operator to:
template <typename Class>
struct result<Class(double)>
{
typedef std::string type;
};
int and a double, returning a size_t
gets:
template <typename Class>
struct result<Class(int, double)>
{
typedef size_t type;
};
<tuple> must have been included.
Whereas std::pair containers have limited functionality and only support
two members, tuples have slightly more functionality and may contain an
unlimited number of different data types. In that respect a tuple can be
thought of the `template's answer to C's struct'.
A tuple's generic declaration (and definition) uses the variadic template notation:
template <class ...Types>
class tuple;
Here is an example of its use:
typedef std::tuple<int, double &, std::string, char const *> tuple_idsc;
double pi = 3.14;
tuple_idsc idsc(59, pi, "hello", "fixed");
// access a field:
std::get<2>(idsc) = "hello world";
The
std::get<idx>(tupleObject) function template returns a
reference to the idx+sup(th) field of the tuple tupleObject. Index 0
returns a reference to its first value. The index is specified as the function
template's non-type template argument.
Tuples may be constructed without specifying initial values. Primitive types are initialized to zeroes; class type fields are initialized by their default constructors. Be aware that in some situations the construction of a tuple may succeed but its use may fail. Consider:
tuple<int &> empty;
cout << get<0>(empty);
Here the tuple empty cannot be used as its int & field is an
undefined reference. Empty's construction, however, succeeds.
Tuples may be assigned to each other if their type lists are identical; if supported by their constituent types copy constructors are available as well. Copy construction and assignment is also available if a right-hand type can be converted to its matching left-hand type or if the left-hand type can be constructed from the matching right-hand type. Tuples (matching in number and (convertible) types) can be compared using relational operators as long as their constituent types support comparisons. In this respect tuples are like pairs.
Tuples offer the following static elements (using compile-time initialization):
std::tuple_size<Tuple>::value returns the number of
types defined for the tuple type Tuple. Example:
cout << tuple_size<tuple_idsc>::value << '\n'; // displays: 4
std::tuple_element<idx, Typle>::type
returns the type of element idx of Tuple. Example:
tuple_element<2, tuple_idsc>::type text; // defines std::string text
The unpack operator can also be used to forward the arguments of a
constructor to a tuple data member. Consider a class Wrapper that is
defined as a variadic template:
template <typename ... Params>
class Wrapper
{
...
public:
Wrapper(Params && ... params);
};
This class may be given a tuple data member which should be initialized by
the types and values that are used when initializing an object of the class
Wrapper using perfect
forwarding. Comparable to the way a class may inherit from its template types
(cf. section 21.5.3) it may forward its types and constructor arguments
to its tuple data member:
template <typename ... Params>
class Wrapper
{
std::tuple<Params ...> d_tuple; // same types as used for
// Wrapper itself
public:
Wrapper(Params && ... params)
: // initialize d_tuple with
// Wrapper's arguments
d_tuple(std::forward<Params>(params) ...)
{}
};
Under the C++0x standard a literal, e.g., 1013, can be looked at in two ways: as a raw literal in which the literal as ASCII string or series of characters is passed to a processing function or as a cooked literal in which case the compiler already performs some conversion.
To extend a literal either a processing function or a variadic function template must be defined. Both functions must start with an underscore and all user defined literals are suffixes.
Using Type to represent the resulting type of the literal a a (raw)
literal can be declared using the following syntax (the initial underscore in
_functionName is required):
Type operator "" _functionName(char const *text [, size_t len]);
Here, `, size_t
len' is an optional parameter receiving text's length (as returned by
C's
strlen function)
The function _functionName is implemented by the software engineer and
must return a value of type Type. Here is an example of the definition and
use of an user defined literal _kmh returning a double:
double _kmh(char const *speed)
{
std::istringstream in(speed);
double ret;
in >> ret;
return ret;
}
double operator "" _kmh(char const *speed);
double value = "120"_kmh;
typedef keyword is frequently used to define `shorthand' notations
for extensive type names. Consider the following example:
typedef
std::map<std::shared_ptr<KeyType>, std::vector<std::set<std::string>>>
MapKey_VectorStringSet;
MapKey_VectorStringSet mapObject;
In situations like this the actual type of the key or the data stored in
the map's vector might vary. Some may store string objects, other may
store double values.
A class may be defined based on the above map allowing the
specification of the final key and value types. Example:
template <typename DeepKey, typename DeepValue>
class DeepMap: public std::map<std::shared_ptr<DeepKey>,
std::vector<std::set<DeepMap>>>
{};
This increases the flexibility of the initially defined map as it is now
possible to write
DeepMap<KeyType, std::string> mapObject;
The C++0x standard adds to this the possibility to pre-specify either of
these template type parameters. To preset the key's type to std::string
leaving the data type open for later specification the following
template using declaration
syntax is
available:
template<typename DeepValue>
using MapString_DeepValue = DeepMap<std::string, DeepValue>;
MapString_DeepValue<size_t> specialMap;
The specialMap now uses shared_ptr<string> for its key and
vector<set<size_t>> for its data type. A similar shorthand notation can
be used to pre-specify the value type leaving the key type open for later
specification.
In addition to the above template syntax the
using keyword
can also be used to separate a typedef name from a complex type
definition. Consider constructing a typedef defining a pointer to a function
expecting and returning a double. The traditional way to define such a
type is as follows:
typedef double (*PFD)(double);
In addition the C++0x standard offers the following alternative syntax:
using PFD = double (*)(double);
Template parameters are also specified when default template parameter
values are specified albeit that in that case the compiler provides the
defaults (cf. section 21.4 where double is used as the default
type to use for the template's DataType parameter). The actual values or
types of template parameters are
never deduced from arguments as is done with function template
parameters. So to define a Matrix of complex-valued elements, the
following syntax is used:
Matrix<3, 5, std::complex> complexMatrix;
Since the class template Matrix uses a default data type
a matrix of double-valued elements can be defined like this:
Matrix<3, 5> doubleMatrix;
A class template object may be declared using the keyword
extern.
For example, to declare the matrix complexMatrix use:
extern Matrix<3, 5, std::complex> complexMatrix;
A class template declaration suffices to compile return values or
parameters that are of class template types. Example: the following source
file may be compiled, although the compiler hasn't seen the definition of the
Matrix class template. Generic classes and (partial) specializations may
all be declared. A function expecting or returning a class template object,
reference, or parameter automatically becomes a function template itself. This
is necessary to allow the compiler to tailor the function to the types of
various actual arguments that may be passed to the function:
#include <cstddef>
template <size_t Rows, size_t Columns, typename DataType = double>
class Matrix;
template <size_t Columns, typename DataType>
class Matrix<1, Columns, DataType>;
Matrix<1, 12> *function(Matrix<2, 18, size_t> &mat);
When class templates are used the compiler must first have seen their
implementations. So, template member functions must be known to the compiler
when the template is instantiated. This does not mean that all members of
a template class are instantiated or must have been seen when a class template
object is defined.
The compiler only instantiates those members that are actually used. This
is illustrated by the following simple class Demo that has two
constructors and two members. When we use one constructor and call one member
in main note the sizes of the resulting object file and executable
program. Next the class definition is modified in that the unused constructor
and member are commented out. Again we compile and link the program. Now
observe that these latter sizes are identical to the former sizes. There are
other ways to illustrate that only used members are instantiated. The
nm
program could be used. It shows the symbolic contents of object files. Using
nm we'll reach the same conclusion: only template member functions that
are actually used are instantiated. Here is the class template Demo that
was used for our little experiment. In main only the first constructor
and the first member function are called and thus only these members were
instantiated:
#include <iostream>
template <typename Type>
class Demo
{
Type d_data;
public:
Demo();
Demo(Type const &value);
void member1();
void member2(Type const &value);
};
template <typename Type>
Demo<Type>::Demo()
:
d_data(Type())
{}
template <typename Type>
void Demo<Type>::member1()
{
d_data += d_data;
}
// the following members should be commented out before
// compiling for the 2nd time:
template <typename Type>
Demo<Type>::Demo(Type const &value)
:
d_data(value)
{}
template <typename Type>
void Demo<Type>::member2(Type const &value)
{
d_data += value;
}
int main()
{
Demo<int> demo;
demo.member1();
}
Code not depending on template parameters is verified by the compiler when
the template is defined. If a member function in a class template uses a
qsort function, then qsort does not depend on a template
parameter. Consequently, qsort must be known to the compiler when it
encounters qsort's function call. In practice this implies that the
<cstdlib> header file must have been processed by the compiler before it
is able to compile the class template definition.
On the other hand, if a template defines a <typename Ret> template
type parameter to parameterize the return type of some template member
function as in:
Ret member();
then the compiler may encounter member or the class to which
member belongs in the following locations:
member. It won't accept a definition or declaration like Ret &&
*member, because C++ does not support functions returning pointers to
rvalue references. Furthermore, it will check whether the actual type name
that is used for instantiating the object is valid. This type name must be
known to the compiler at the object's point of instantiation.
Ret parameter must have been specified (or deduced) and at this point
member's
statements that depend on the Ret template parameter are checked for
syntactic correctness. For example, if member contains a statement
like
Ret tmp(Ret(), 15);
then this is in principle a syntactically valid statement. However, when
Ret = int the statement fails to compile as int does not have a
constructor expecting two int arguments. Note that this is not a
problem when the compiler instantiates an object of member's class. At
the point of instantiation of the object its member function `member' is
not instantiated and so the invalid int construction remains undetected.
Like ordinary classes, class templates may declare other functions and
classes as their friends. Conversely, ordinary classes may declare template
classes as their friends. Here too, the friend is constructed as a special
function or class augmenting or supporting the functionality of the declaring
class. Although the friend keyword can be used by any type of class
(ordinary or template) when using class templates the following cases
should be distinguished:
ostream objects.
If the actual values of the friend's template parameters must be equal
to the template parameters of the class declaring the friend, the friend is
said to be a
bound friend
class or function template. In
this case the template parameters of the template specifying the friend
declaration determine (bind) the values of the template parameters of the
friend class or function. Bound friends result in a one-to-one correspondence
between the template's parameters and the friend's template parameters.
In this case an unbound friend class or function template is declared. The template parameters of the friend class or function template remain to be specified and are not related in some predefined way to the template parameters of the class declaring the friend. If a class template has data members of various types, specified by its template parameters and another class should be allowed direct access to these data members we want to specify any of the current template arguments when specifying such a friend. Rather than specifying multiple bound friends, a single generic (unbound) friend may be declared, specifying the friend's actual template parameters only when this is required.
Concrete classes and ordinary functions can be declared as friends, but before a single member function of a class can be declared as a friend, the compiler must have seen the class interface declaring that member. Let's consider the various possibilities:
void function(std::vector<Type> &vector)
unless function itself is a template, it is not immediately clear how
and why such a friend should be constructed. One reason could be to allow the
function access to the class's private static members. In addition such
friends could instantiate objects of classes that declare them as their
friends. This would allow the friend functions direct access to such object's
private members. For example:
template <typename Type>
class Storage
{
friend void basic();
static size_t s_time;
std::vector<Type> d_data;
public:
Storage();
};
template <typename Type>
size_t Storage<Type>::s_time = 0;
template <typename Type>
Storage<Type>::Storage()
{}
void basic()
{
Storage<int>::s_time = time(0);
Storage<double> storage;
std::random_shuffle(storage.d_data.begin(), storage.d_data.end());
}
template <typename Type>
class Composer
{
friend class Friend;
std::vector<Type> d_data;
public:
Composer();
};
class Friend
{
Composer<int> d_ints;
public:
Friend(std::istream &input);
};
inline::Friend::Friend(std::istream &input)
{
std::copy(std::istream_iterator<int>(input),
std::istream_iterator<int>(),
back_inserter(d_ints.d_data));
}
randomizer is declared as a friend of the class
Composer:
template <typename Type>
class Composer;
class Friend
{
Composer<int> *d_ints;
public:
Friend(std::istream &input);
void randomizer();
};
template <typename Type>
class Composer
{
friend void Friend::randomizer();
std::vector<Type> d_data;
public:
Composer(std::istream &input)
{
std::copy(std::istream_iterator<int>(input),
std::istream_iterator<int>(),
back_inserter(d_data));
}
};
inline Friend::Friend(std::istream &input)
:
d_ints(new Composer<int>(input))
{}
inline void Friend::randomizer()
{
std::random_shuffle(d_ints->d_data.begin(), d_ints->d_data.end());
}
In this example Friend::d_ints is a pointer member. It
cannot be a Composer<int> object as the Composer class interface
hasn't yet been seen by the compiler when it reads Friend's class
interface. Disregarding this and defining a data member Composer<int>
d_ints results in the compiler generating the error
error: field `d_ints' has incomplete type
Incomplete type, as the compiler at this points knows of the existence of
the class Composer but as it hasn't seen Composer's interface it
doesn't know what size the d_ints data member will have.
!key1.find(key2) is probably more useful. For
the current example, operator== is acceptable:
template <typename Key, typename Value>
class Dictionary
{
friend Dictionary<Key, Value>
subset<Key, Value>(Key const &key,
Dictionary<Key, Value> const &dict);
std::map<Key, Value> d_dict;
public:
Dictionary();
};
template <typename Key, typename Value>
Dictionary<Key, Value>
subset(Key const &key, Dictionary<Key, Value> const &dict)
{
Dictionary<Key, Value> ret;
std::remove_copy_if(dict.d_dict.begin(), dict.d_dict.end(),
std::inserter(ret.d_dict, ret.d_dict.begin()),
std::bind2nd(std::equal_to<Key>(), key));
return ret;
}
Iterator is
declared as a friend of a class Dictionary. Thus, the Iterator is able
to access Dictionary's private data. There are some interesting points to
note here:
template <typename Key, typename Value>
class Iterator;
template <typename Key, typename Value>
class Dictionary
{
friend class Iterator<Key, Value>;
template <typename Key, typename Value>
template <typename Key2, typename Value2>
Iterator<Key2, Value2> Dictionary<Key, Value>::begin()
{
return Iterator<Key, Value>(*this);
}
template <typename Key, typename Value>
template <typename Key2, typename Value2>
Iterator<Key2, Value2> Dictionary<Key, Value>::subset(Key const &key)
{
return Iterator<Key, Value>(*this).subset(key);
}
Dictionary it can safely
define a std::map data member that is initialized by the friend class's
constructor. The constructor may then access the Dictionary's private data
member d_dict:
template <typename Key, typename Value>
class Iterator
{
std::map<Key, Value> &d_dict;
public:
Iterator(Dictionary<Key, Value> &dict)
:
d_dict(dict.d_dict)
{}
Iterator member begin may return a map
iterator. Since the compiler does not know what the instantiation of the map
will look like, a map<Key, Value>::iterator is a template subtype. So it
cannot be used as-is, but it must be prefixed by
typename (see the function begin's return type in the next example):
template <typename Key, typename Value>
typename std::map<Key, Value>::iterator Iterator<Key, Value>::begin()
{
return d_dict.begin();
}
Dictionary
should be able to construct an Iterator (maybe because we consider
Iterator to be conceptually tied to Dictionary). This can be
accomplished by defining Iterator's constructor in its private section,
and declaring Dictionary Iterator's friend. Consequently, only a
Dictionary can create an Iterator. By declaring Iterator's
constructor as a bound friend we ensure that it can only create
Iterators using template parameters identical to its own. Here is how it's
done:
template <typename Key, typename Value>
class Iterator
{
friend Dictionary<Key, Value>::Dictionary();
std::map<Key, Value> &d_dict;
Iterator(Dictionary<Key, Value> &dict);
public:
In this example, Dictionary's constructor is Iterator's
friend. The friend is a template member. Other members can be declared as
a class's friend as well. In those cases their prototypes must be used, also
specifying the types of their return values. Assuming that
std::vector<Value> sortValues()
is a member of Dictionary then the matching bound friend declaration
is:
friend std::vector<Value> Dictionary<Key, Value>::sortValues();
template <typename T> // a function template
void fun(T *t)
{
t->not_public();
};
template <typename X> // a class template
class A
{ // fun() is used as friend bound to A,
// instantiated for X, whatever X may be
friend void fun<A<X>>(A<X> *);
public:
A();
private:
void not_public();
};
template <typename X>
A<X>::A()
{
fun(this);
}
template <typename X>
void A<X>::not_public()
{}
int main()
{
A<int> a;
fun(&a); // fun instantiated for A<int>.
}
Here are the syntactic conventions declaring unbound friends:
template <typename Iterator, typename Class, typename Data>
Class &ForEach(Iterator begin, Iterator end, Class &object,
void (Class::*member)(Data &));
This function template can be declared as an unbound friend in the
following class template Vector2:
template <typename Type>
class Vector2: public std::vector<std::vector<Type> >
{
template <typename Iterator, typename Class, typename Data>
friend Class &ForEach(Iterator begin, Iterator end, Class &object,
void (Class::*member)(Data &));
...
};
If the function template is defined inside some namespace this namespace
must be mentioned as well. Assuming that ForEach is defined in the
namespace FBB its friend declaration becomes:
template <typename Iterator, typename Class, typename Data>
friend Class &FBB::ForEach(Iterator begin, Iterator end, Class &object,
void (Class::*member)(Data &));
The following example illustrates the use of an unbound friend. The class
Vector2 stores vectors of elements of template type parameter
Type. Its process member allows ForEach to call its private
rows member. The rows member, in turn, uses another ForEach to
call its private columns member. Consequently, Vector2 uses two
instantiations of ForEach which is a clear hint for using an unbound
friend. It is assumed that Type class objects can be inserted into
ostream objects (the definition of the ForEach function template can
be found in the cplusplus.yo.zip archive at
http://sourceforge.net/projects/cppannotations/). Here is the program:
template <typename Type>
class Vector2: public std::vector<std::vector<Type> >
{
typedef typename Vector2<Type>::iterator iterator;
template <typename Iterator, typename Class, typename Data>
friend Class &ForEach(Iterator begin, Iterator end, Class &object,
void (Class::*member)(Data &));
public:
void process();
private:
void rows(std::vector<Type> &row);
void columns(Type &str);
};
template <typename Type>
void Vector2<Type>::process()
{
ForEach<iterator, Vector2<Type>, std::vector<Type> >
(this->begin(), this->end(), *this, &Vector2<Type>::rows);
}
template <typename Type>
void Vector2<Type>::rows(std::vector<Type> &row)
{
ForEach(row.begin(), row.end(), *this,
&Vector2<Type>::columns);
std::cout << '\n';
}
template <typename Type>
void Vector2<Type>::columns(Type &str)
{
std::cout << str << " ";
}
using namespace std;
int main()
{
Vector2<string> c;
c.push_back(vector<string>(3, "Hello"));
c.push_back(vector<string>(2, "World"));
c.process();
}
/*
Generated output:
Hello Hello Hello
World World
*/
template <typename Type>
class PtrVector
{
template <typename Iterator, typename Class>
friend class Wrapper; // unbound friend class
};
All members of the class template Wrapper may now instantiate
PtrVectors using any actual type for its Type parameter. This allows
the Wrapper instantiation to access all of PtrVector's private
members.
PtrVector
declares Wrapper::begin as its friend. Note the forward declaration of
the class Wrapper:
template <typename Iterator>
class Wrapper;
template <typename Type>
class PtrVector
{
template <typename Iterator> friend
PtrVector<Type> Wrapper<Iterator>::begin(Iterator const &t1);
...
};
Consider the following base class:
template<typename T>
class Base
{
T const &t;
public:
Base(T const &t);
};
The above class is a class template that can be used as a base class for
the following derived class template Derived:
template<typename T>
class Derived: public Base<T>
{
public:
Derived(T const &t);
};
template<typename T>
Derived<T>::Derived(T const &t)
:
Base(t)
{}
Other combinations are also possible. The base class may be instantiated
by specifying template arguments, turning the derived class into an ordinary
class (showing a class object's definition as well):
class Ordinary: public Base<int>
{
public:
Ordinary(int x);
};
inline Ordinary::Ordinary(int x)
:
Base(x)
{}
Ordinary ordinary(5);
This approach allows us to
add functionality to a class template, without the need for constructing a
derived class template.
Class template derivation pretty much follows the same rules as ordinary
class derivation, not involving class templates. Some subtleties that are
specific for class template derivation may easily cause confusion like the use
of this when members of a template base class are called from a derived
class. The reasons for using this are discussed in section 22.1. In
the upcoming sections the focus will be on the class derivation proper.
map can
easily be used in combination with the find_if() generic algorithm
(section 19.1.16), it requires the construction of a class and at least
two additional function objects of that class. If this is considered too much
overhead then extending a class template with tailor-made functionality
might be considered.
Example: a program executing commands entered at the keyboard might accept all
unique initial abbreviations of the commands it defines. E.g., the command
list might be entered as l, li, lis or list. By deriving a class
Handler from
map<string, void (Handler::*)(string const &cmd)>
and by defining a member function process(string const &cmd) to do the
actual command processing a program might simply execute the following
main() function:
int main()
{
string line;
Handler cmd;
while (getline(cin, line))
cmd.process(line);
}
The class Handler itself is derived from a std::map, in which
the map's values are pointers to Handler's member functions, expecting the
command line entered by the user. Here are Handler's characteristics:
std::map, expecting the command
associated with each command-processing member as its keys. Since
Handler uses the map merely to define associations between
the commands and the processing
member functions and to make available map's typedefs, private
derivation is used:
class Handler: private std::map<std::string,
void (Handler::*)(std::string const &cmd)>
s_cmds is an array of Handler::value_type values, and
s_cmds_end is a constant pointer pointing beyond the array's last element:
static value_type s_cmds[];
static value_type *const s_cmds_end;
inline Handler::Handler()
:
std::map<std::string,
void (Handler::*)(std::string const &cmd)>
(s_cmds, s_cmds_end)
{}
process iterates along the map's elements. Once the
first word on the command line matches the initial characters of the command,
the corresponding command is executed. If no such command is found, an error
message is issued:
void Handler::process(std::string const &line)
{
istringstream istr(line);
string cmd;
istr >> cmd;
for (iterator it = begin(); it != end(); it++)
{
if (it->first.find(cmd) == 0)
{
(this->*it->second)(line);
return;
}
}
cout << "Unknown command: " << line << '\n';
}
The class template SortVector presented below is derived from the
existing class template std::vector. It allows us to perform a
hierarchic sort of its elements using any ordering of any data members
its data elements may contain. To accomplish this there is but one
requirement. SortVector's data type must offer dedicated member
functions comparing its members.
For example, if SortVector's data type is an object of class
MultiData, then MultiData should implement member functions having the
following prototypes for each of its data members which can be compared:
bool (MultiData::*)(MultiData const &rhv)
So, if MultiData has two data members, int d_value and
std::string d_text and both may be used by a hierarchic sort, then
MultiData should offer the following two members:
bool intCmp(MultiData const &rhv); // returns d_value < rhv.d_value
bool textCmp(MultiData const &rhv); // returns d_text < rhv.d_text
Furthermore, as a convenience it is assumed that operator<< and
operator>> have been defined for MultiData objects.
The class template SortVector is directly derived from the template
class std::vector. Our implementation inherits all members from that base
class. It also offers two simple constructors:
template <typename Type>
class SortVector: public std::vector<Type>
{
public:
SortVector()
{}
SortVector(Type const *begin, Type const *end)
:
std::vector<Type>(begin, end)
{}
Its member hierarchicSort is the true raison d'être for the
class. It defines the hierarchic sort criteria. It expects
a pointer to a series of pointers to member functions of the class
Type as well as a size_t representing the size of the array.
The array's first element indicates Type's most significant sort
criterion, the array's last element indicates the class's least significant
sort criterion. Since the
stable_sort generic algorithm was designed
explicitly to support hierarchic sorting, the member uses this generic
algorithm to sort SortVector's elements. With hierarchic sorting, the
least significant criterion should be sorted first. hierarchicSort's
implementation is therefore easy. It requires a support class SortWith
whose objects are initialized by the addresses of the member functions passed
to the hierarchicSort() member:
template <typename Type>
void SortVector<Type>::hierarchicSort(
bool (Type::**arr)(Type const &rhv) const,
size_t n)
{
while (n--)
stable_sort(this->begin(), this->end(), SortWith<Type>(arr[n]));
}
The class SortWith is a simple wrapper class around a pointer to
a predicate function. Since it depends on SortVector's actual data
type the class SortWith must also be a class template:
template <typename Type>
class SortWith
{
bool (Type::*d_ptr)(Type const &rhv) const;
SortWith's constructor receives a pointer to a predicate function and
initializes the class's d_ptr data member:
template <typename Type>
SortWith<Type>::SortWith(bool (Type::*ptr)(Type const &rhv) const)
:
d_ptr(ptr)
{}
Its binary predicate member (operator()) must return true if its
first argument should eventually be placed ahead of its second argument:
template <typename Type>
bool SortWith<Type>::operator()(Type const &lhv, Type const &rhv) const
{
return (lhv.*d_ptr)(rhv);
}
The following main function provides an illustration:
SortVector object is created for MultiData objects.
It uses the
copy generic algorithm to fill the SortVector object from
information appearing at the program's standard input stream. Having
initialized the object its elements are displayed to the standard output
stream:
SortVector<MultiData> sv;
copy(istream_iterator<MultiData>(cin),
istream_iterator<MultiData>(),
back_inserter(sv));
bool (MultiData::*arr[])(MultiData const &rhv) const =
{
&MultiData::textCmp,
&MultiData::intCmp,
};
sv.hierarchicSort(arr, 2);
MultiData's
member functions are swapped, and the previous step is repeated:
swap(arr[0], arr[1]);
sv.hierarchicSort(arr, 2);
echo a 1 b 2 a 2 b 1 | a.out
This results in the following output:
a 1 b 2 a 2 b 1
====
a 1 a 2 b 1 b 2
====
a 1 b 1 a 2 b 2
====
This approach may be used for all class templates having member functions whose implementations do not depend on template parameters. These members may be defined in a separate class which is then used as a base class of the class template derived from it.
As an illustration of this approach we'll develop such a class template
below. We'll develop a class Table derived from an ordinary class
TableType. The class Table displays elements of some type in a
table having a configurable number of columns. The elements are either
displayed horizontally (the first k elements occupy the first row) or
vertically (the first r elements occupy a first column).
When displaying the table's elements they are inserted into a stream. The
table is handled by a separate class (TableType), implementing the table's
presentation. Since the table's elements are inserted into a stream, the
conversion to text (or string) is implemented in Table, but the
handling of the strings themselves is left to TableType. We'll cover some
characteristics of TableType shortly, concentrating on Table's
interface first:
Table is a class template, requiring only one template
type parameter: Iterator referring to an iterator to some data type:
template <typename Iterator>
class Table: public TableType
{
Table doesn't need any data members. All data manipulations are
performed by TableType.
Table has two constructors. The constructor's first two
parameters are Iterators used to iterate over the elements that must be
entered into the table. The constructors require us to specify
the number of columns we would like our table to have as well as a
FillDirection. FillDirection is an enum,
defined by TableType, having values HORIZONTAL and VERTICAL. To
allow Table's users to exercise control over headers, footers, captions,
horizontal and vertical separators, one constructor has TableSupport
reference parameter. The class TableSupport will be developed later as a
virtual class allowing clients to exercise this control. Here are the class's
constructors:
Table(Iterator const &begin, Iterator const &end,
size_t nColumns, FillDirection direction);
Table(Iterator const &begin, Iterator const &end,
TableSupport &tableSupport,
size_t nColumns, FillDirection direction);
Table's only two public members. Both
constructors use a base class initializer to initialize their TableType
base class and then call the class's private member fill to insert data
into the TableType base class object. Here are the constructor's
implementations:
template <typename Iterator>
Table<Iterator>::Table(Iterator const &begin, Iterator const &end,
TableSupport &tableSupport,
size_t nColumns, FillDirection direction)
:
TableType(tableSupport, nColumns, direction)
{
fill(begin, end);
}
template <typename Iterator>
Table<Iterator>::Table(Iterator const &begin, Iterator const &end,
size_t nColumns, FillDirection direction)
:
TableType(nColumns, direction)
{
fill(begin, end);
}
fill member iterates over the range of elements
[begin, end), as defined by the constructor's first two parameters.
As we will see shortly, TableType defines a protected data member
std::vector<std::string> d_string. One of the requirements of the data
type to which the iterators point is that this data type can be inserted into
streams. So, fill uses a ostringstream object to obtain the textual
representation of the data, which is then appended to d_string:
template <typename Iterator>
void Table<Iterator>::fill(Iterator it, Iterator const &end)
{
while (it != end)
{
std::ostringstream str;
str << *it++;
d_string.push_back(str.str());
}
init();
}
This completes the implementation of the class Table. Note that this
class template only has three members, two of them constructors. Therefore, in
most cases only two function templates will have to be instantiated: a
constructor and the class's fill member. For example, the following
defines a table having four columns, vertically filled by strings
extracted from the standard input stream:
Table<istream_iterator<string> >
table(istream_iterator<string>(cin), istream_iterator<string>(),
4, TableType::Vertical);
The fill-direction is specified as
TableType::VERTICAL. It could also have been specified using Table,
but since Table is a class template its specification would have been
slightly more complex: Table<istream_iterator<string> >::VERTICAL.
Now that the Table derived class has been designed, let's turn our
attention to the class TableType. Here are its essential characteristics:
Table's base
class.
d_colWidth, a
vector storing the width of the widest element per column and d_indexFun,
pointing to the class's member function returning the element in
table[row][column], conditional to the table's fill
direction. TableType also uses a TableSupport pointer and a
reference. The constructor not requiring a TableSupport object uses the
TableSupport * to allocate a (default) TableSupport object and then
uses the TableSupport & as the object's alias. The other constructor
initializes the pointer to 0 and uses the reference data member to refer to
the TableSupport object provided by its parameter. Alternatively, a static
TableSupport object could have been used to initialize the reference data
member in the former constructor. The remaining private data members are
probably self-explanatory:
TableSupport *d_tableSupportPtr;
TableSupport &d_tableSupport;
size_t d_maxWidth;
size_t d_nRows;
size_t d_nColumns;
WidthType d_widthType;
std::vector<size_t> d_colWidth;
size_t (TableType::*d_widthFun)
(size_t col) const;
std::string const &(TableType::*d_indexFun)
(size_t row, size_t col) const;
string objects populating the table are stored in a
protected data member:
std::vector<std::string> d_string;
TableSupport object:
#include "tabletype.ih"
TableType::TableType(TableSupport &tableSupport, size_t nColumns,
FillDirection direction)
:
d_tableSupportPtr(0),
d_tableSupport(tableSupport),
d_maxWidth(0),
d_nRows(0),
d_nColumns(nColumns),
d_widthType(COLUMNWIDTH),
d_colWidth(nColumns),
d_widthFun(&TableType::columnWidth),
d_indexFun(direction == HORIZONTAL ?
&TableType::hIndex
:
&TableType::vIndex)
{}
d_string has been filled, the table is initialized by
Table::fill. The init protected member resizes d_string so
that its size is exactly rows x columns, and it determines the maximum
width of the elements per column. Its implementation is straightforward:
#include "tabletype.ih"
void TableType::init()
{
if (!d_string.size()) // no elements
return; // then do nothing
d_nRows = (d_string.size() + d_nColumns - 1) / d_nColumns;
d_string.resize(d_nRows * d_nColumns); // enforce complete table
// determine max width per column,
// and max column width
for (size_t col = 0; col < d_nColumns; col++)
{
size_t width = 0;
for (size_t row = 0; row < d_nRows; row++)
{
size_t len = stringAt(row, col).length();
if (width < len)
width = len;
}
d_colWidth[col] = width;
if (d_maxWidth < width) // max. width so far.
d_maxWidth = width;
}
}
insert is used by the insertion operator
(operator<<) to insert a Table into a stream. First it informs the
TableSupport object about the table's dimensions. Next it displays the
table, allowing the TableSupport object to write headers, footers and
separators:
#include "tabletype.ih"
ostream &TableType::insert(ostream &ostr) const
{
if (!d_nRows)
return ostr;
d_tableSupport.setParam(ostr, d_nRows, d_colWidth,
d_widthType == EQUALWIDTH ? d_maxWidth : 0);
for (size_t row = 0; row < d_nRows; row++)
{
d_tableSupport.hline(row);
for (size_t col = 0; col < d_nColumns; col++)
{
size_t colwidth = width(col);
d_tableSupport.vline(col);
ostr << setw(colwidth) << stringAt(row, col);
}
d_tableSupport.vline();
}
d_tableSupport.hline();
return ostr;
}
cplusplus.yo.zip archive contains TableSupport's full
implementation. This implementation is found in the directory
yo/classtemplates/examples/table. Most of its remaining members are
private. Among those, these two members return table element
[row][column] for, respectively, a horizontally filled table and a
vertically filled table:
inline std::string const &TableType::hIndex(size_t row, size_t col) const
{
return d_string[row * d_nColumns + col];
}
inline std::string const &TableType::vIndex(size_t row, size_t col) const
{
return d_string[col * d_nRows + row];
}
The support class TableSupport is used to display headers, footers,
captions and separators. It has four virtual members to perform those tasks
(and, of course, a virtual constructor):
hline(size_t rowIndex): called just before the elements in row
rowIndex will be displayed.
hline(): called immediately after displaying the final row.
vline(size_t colIndex): called just before the element in column
colIndex will be displayed.
vline(): called immediately after displaying all elements in a row.
cplusplus.yo.zip archive for the full
implementation of the classes Table, TableType and TableSupport.
Here is a little program showing their use:
/*
table.cc
*/
#include <fstream>
#include <iostream>
#include <string>
#include <iterator>
#include <sstream>
#include "tablesupport/tablesupport.h"
#include "table/table.h"
using namespace std;
using namespace FBB;
int main(int argc, char **argv)
{
size_t nCols = 5;
if (argc > 1)
{
istringstream iss(argv[1]);
iss >> nCols;
}
istream_iterator<string> iter(cin); // first iterator isn't const
Table<istream_iterator<string> >
table(iter, istream_iterator<string>(), nCols,
argc == 2 ? TableType::VERTICAL : TableType::HORIZONTAL);
cout << table << '\n';
return 0;
}
/*
Example of generated output:
After: echo a b c d e f g h i j | demo 3
a e i
b f j
c g
d h
After: echo a b c d e f g h i j | demo 3 h
a b c
d e f
g h i
j
*/
PtrVector, a class iterator is defined. The nested class receives its
information from its surrounding class, a PtrVector<Type> class. Since
this surrounding class should be the only class constructing its iterators,
iterator's constructor is made
private and the surrounding class is
given access to the private members of iterator using a
bound friend declaration.
Here is the initial section of PtrVector's class interface:
template <typename Type>
class PtrVector: public std::vector<Type *>
This shows that the std::vector base class stores pointers to Type
values, rather than the values themselves. Of course, a destructor is now
required as the (externally allocated) memory for the Type objects must
eventually be freed. Alternatively, the allocation might be part of
PtrVector's tasks, when it stores new elements. Here it is assumed that
PtrVector's clients do the required allocations and that the destructor is
implemented later on.
The nested class defines its constructor as a private member, and allows
PtrVector<Type> objects access to its private members. Therefore only
objects of the surrounding PtrVector<Type> class type are allowed to
construct their iterator objects. However, PtrVector<Type>'s clients
may construct copies of the PtrVector<Type>::iterator objects they
use.
Here is the nested class iterator, using a (required) friend
declaration. Note the use of the typename keyword: since
std::vector<Type *>::iterator depends on a template parameter, it is not
yet an instantiated class. Therefore iterator becomes an implicit
typename. The compiler issues a warning if typename has been
omitted. Here is the class interface:
class iterator
{
friend class PtrVector<Type>;
typename std::vector<Type *>::iterator d_begin;
iterator(PtrVector<Type> &vector);
public:
Type &operator*();
};
The implementation of the members shows that the base class's begin
member is called to initialize d_begin. PtrVector<Type>::begin's
return type must again be preceded by typename:
template <typename Type>
PtrVector<Type>::iterator::iterator(PtrVector<Type> &vector)
:
d_begin(vector.std::vector<Type *>::begin())
{}
template <typename Type>
Type &PtrVector<Type>::iterator::operator*()
{
return **d_begin;
}
The remainder of the class is simple. Omitting all other functions that
might be implemented, the function begin returns a newly constructed
PtrVector<Type>::iterator object. It may call the constructor since the
class iterator declared its surrounding class as its friend:
template <typename Type>
typename PtrVector<Type>::iterator PtrVector<Type>::begin()
{
return iterator(*this);
}
Here is a simple skeleton program, showing how the nested class
iterator might be used:
int main()
{
PtrVector<int> vi;
vi.push_back(new int(1234));
PtrVector<int>::iterator begin = vi.begin();
std::cout << *begin << '\n';
}
Nested
enumerations and
nested typedefs can also be defined by class templates. The class Table,
mentioned before (section 21.10.3) inherited the enumeration
TableType::FillDirection. If Table would have been implemented as a
full class template, then this enumeration would have been defined in
Table itself as:
template <typename Iterator>
class Table: public TableType
{
public:
enum FillDirection
{
HORIZONTAL,
VERTICAL
};
...
};
In this case, the actual value of the template type parameter must be
specified when referring to a FillDirection value or to its type. For
example (assuming iter and nCols are defined as in section
21.10.3):
Table<istream_iterator<string> >::FillDirection direction =
argc == 2 ?
Table<istream_iterator<string> >::VERTICAL
:
Table<istream_iterator<string> >::HORIZONTAL;
Table<istream_iterator<string> >
table(iter, istream_iterator<string>(), nCols, direction);
To ensure that an object of a class is interpreted as a particular type of
iterator, the class must be derived from the class
std::iterator. Before a class can be derived from this
class the
<iterator> header file must have been included.
In section 18.2 the characteristics of iterators were discussed. All iterators should support an increment operation, a dereference operation and a comparison for (in)equality.
When iterators must be used in the context of generic algorithms they must meet additional requirements. This is caused by the fact that generic algorithms check the types of the iterators they receive. Simple pointers are usually accepted, but if an iterator-object is used it must be able to specify the kind of iterator it represents.
To ensure that an object of a class is interpreted as a particular type of
iterator, the class must be derived from the class iterator. The
particular
type of iterator is defined
by the class template's first parameter, and the particular
data type to which the iterator points is defined by
the class template's second parameter.
The type of iterator that is implemented by the derived class
is specified using a so-called
iterator_tag, provided as the
first template argument of the class iterator. For the five basic iterator
types, these tags are:
std::input_iterator_tag. This tag defines an
InputIterator. Iterators of this type allow reading operations, iterating
from the first to the last element of the series to which the iterator refers.
std::output_iterator_tag. This tag defines an
OutputIterator. Iterators of this type allow for assignment operations,
iterating from the first to the last element of the series to which the
iterator refers.
std::forward_iterator_tag. This tag defines a
ForwardIterator. Iterators of this type allow reading and
assignment operations, iterating from the first to the last element of the
series to which the iterator refers.
std::bidirectional_iterator_tag. This
tag defines a
BidirectionalIterator. Iterators of this type allow reading
and assignment operations, iterating step by step, possibly in alternating
directions, over all elements of the series to which the iterator refers.
std::random_access_iterator_tag. This
tag defines a
RandomAccessIterator. Iterators of this type allow reading
and assignment operations, iterating, possibly in alternating directions,
over all elements of the series to which the iterator refers using any
available (random) stepsize.
Note that iterators are always defined over a certain range
([begin, end)). Increment and decrement operations may result in
undefined behavior of the iterator if the resulting iterator value would refer
to a location outside of this range.
Often, iterators only access the elements of the series to which they refer. Internally, an iterator may use an ordinary pointer but it is hardly ever necessary for the iterator to allocate its own memory. Therefore, as the assignment operator and the copy constructor do not have to allocate any memory, their default implementations usually suffice. For the same reason iterators usually don't require destructors.
Most classes offering members returning iterators do so by having members construct the required iterators that are thereupon returned as objects by those member functions. As the caller of these member functions only has to use or sometimes copy the returned iterator objects, there is usually no need to provide any publicly available constructor, except for the copy constructor. Therefore these constructors are usually defined as private or protected members. To allow an outer class to create iterator objects, the iterator class usually declares the outer class as its friend.
In the following sections the construction of a RandomAccessIterator, the most complex of all iterators, and the construction of a reverse RandomAccessIterator is discussed. The container class for which a random access iterator must be developed may actually store its data elements in many different ways (e.g., using containers or pointers to pointers). Therefore it is difficult to construct a template iterator class which is suitable for a large variety of container classes.
In the following sections the available std::iterator class is used to
construct an inner class representing a random access iterator. The reader
may follow the approach illustrated there to construct iterator classes for
other contexts. An example of such a template iterator class is provided in
section 23.7.
The random access iterator developed in the next sections reaches data elements that are only accessible through pointers. The iterator class is designed as an inner class of a class derived from a vector of string pointers.
unique_ptr and
shared_ptr type objects may be used, though). Although discouraged, we
might be able to use pointer data types in specific contexts. In the following
class StringPtr, an ordinary class is derived from the std::vector
container that uses std::string * as its data type:
#ifndef INCLUDED_STRINGPTR_H_
#define INCLUDED_STRINGPTR_H_
#include <string>
#include <vector>
class StringPtr: public std::vector<std::string *>
{
public:
StringPtr(StringPtr const &other);
~StringPtr();
StringPtr &operator=(StringPtr const &other);
};
#endif
This class needs a destructor: as the object stores string pointers, a
destructor is required to destroy the strings once the StringPtr object
itself is destroyed. Similarly, a copy constructor and overloaded assignment
is required. Other members (in particular: constructors) are not explicitly
declared here as they are not relevant to this section's topic.
Assume that we want to be able to use the sort generic
algorithm with StringPtr objects. This algorithm (see section 19.1.58)
requires two RandomAccessIterators. Although these iterators are available
(via std::vector's begin and end members), they return
iterators to std::string *s, which cannot sensibly be compared.
To remedy this, we may define an internal type StringPtr::iterator,
not returning iterators to pointers, but iterators to the objects these
pointers point to. Once this iterator type is available, we can add the
following members to our StringPtr class interface, hiding the identically
named, but useless members of its base class:
StringPtr::iterator begin(); // returns iterator to the first element
StringPtr::iterator end(); // returns iterator beyond the last
// element
Since these two members return the (proper) iterators, the elements in a
StringPtr object can easily be sorted:
in main()
{
StringPtr sp; // assume sp is somehow filled
sort(sp.begin(), sp.end()); // sp is now sorted
}
To make this all work, the type StringPtr::iterator must be
defined. As suggested by its type name, iterator is a nested type of
StringPtr. To use a StringPtr::iterator in combination
with the sort generic algorithm it must also be a
RandomAccessIterator. Therefore, StringPtr::iterator itself must be
derived from the existing class std::iterator.
To derive a class from std::iterator, both the iterator type and the
data type the iterator points to must be specified. Caveat: our iterator will
take care of the string * dereferencing; so the required data type will be
std::string, and not std::string *. The class iterator
therefore starts its interface as:
class iterator:
public std::iterator<std::random_access_iterator_tag, std::string>
Since its base class specification is quite complex, we could consider
associating this type with a shorter name using the following typedef:
typedef std::iterator<std::random_access_iterator_tag, std::string>
Iterator;
In practical situations, if the type (Iterator) is used only once or
twice, the type definition only adds clutter to the interface, and is better
not used.
Now we're ready to redesign StringPtr's class interface. It offers
members returning (reverse) iterators, and a nested iterator class. The
members will next be discussed in some detail.
class StringPtr: public std::vector<std::string *>
{
public:
class iterator: public
std::iterator<std::random_access_iterator_tag, std::string>
{
friend class StringPtr;
std::vector<std::string *>::iterator d_current;
iterator(std::vector<std::string *>::iterator const ¤t);
public:
iterator &operator--();
iterator const operator--(int);
iterator &operator++();
iterator &operator++(int);
bool operator==(iterator const &other) const;
bool operator!=(iterator const &other) const;
int operator-(iterator const &rhs) const;
std::string &operator*() const;
bool operator<(iterator const &other) const;
iterator const operator+(int step) const;
iterator const operator-(int step) const;
iterator &operator+=(int step); // increment over `n' steps
iterator &operator-=(int step); // decrement over `n' steps
std::string *operator->() const;// access the fields of the
// struct an iterator points
// to. E.g., it->length()
};
typedef std::reverse_iterator<iterator> reverse_iterator;
iterator begin();
iterator end();
reverse_iterator rbegin();
reverse_iterator rend();
};
First we have a look at StringPtr::iterator's characteristics:
iterator defines StringPtr as its friend, so iterator's
constructor may remain private. Only the StringPtr class itself is now
able to construct iterators, which seems like a sensible thing to
do. Under the current implementation, copy-construction should of course
also be possible. Furthermore, since an iterator is already provided by
StringPtr's base class, we can use that iterator to access the information
stored in the StringPtr object.
StringPtr::begin and StringPtr::end may simply return
iterator objects. They are implementated like this:
inline StringPtr::iterator StringPtr::begin()
{
return iterator(this->std::vector<std::string *>::begin());
}
inline StringPtr::iterator StringPtr::end()
{
return iterator(this->std::vector<std::string *>::end());
}
iterator's remaining members are public. It's very easy to
implement them, mainly manipulating and dereferencing the available iterator
d_current. A RandomAccessIterator (which is the most
complex of iterators) requires a series of operators. They usually
have very simple implementations, making them good candidates for
inline-members:
iterator &operator++(); the pre-increment operator:
inline StringPtr::iterator &StringPtr::iterator::operator++()
{
++d_current;
return *this;
}
iterator operator++(int); the post-increment operator:
inline StringPtr::iterator const StringPtr::iterator::operator++(int)
{
return iterator(d_current++);
}
iterator &operator--(); the pre-decrement operator:
inline StringPtr::iterator &StringPtr::iterator::operator--()
{
--d_current;
return *this;
}
iterator operator--(int); the post-decrement operator:
inline StringPtr::iterator const StringPtr::iterator::operator--(int)
{
return iterator(d_current--);
}
iterator &operator=(iterator const &other); the overloaded
assignment operator. Since iterator objects do not allocate
any memory themselves, the default assignment operator will do.
bool operator==(iterator const &rhv) const; testing the equality
of two iterator objects:
inline bool StringPtr::iterator::operator==(iterator const &other) const
{
return d_current == other.d_current;
}
bool operator<(iterator const &rhv) const; testing whether the
left-hand side iterator points to an element of the series located
before the element pointed to by the right-hand side
iterator:
inline bool StringPtr::iterator::operator<(iterator const &other) const
{
return **d_current < **other.d_current;
}
int operator-(iterator const &rhv) const; returning the number of
elements between the element pointed to by the left-hand side
iterator and the right-hand side iterator (i.e., the value to add
to the left-hand side iterator to make it equal to the value of
the right-hand side iterator):
inline int StringPtr::iterator::operator-(iterator const &rhs) const
{
return d_current - rhs.d_current;
}
Type &operator*() const; returning a reference to the object to
which the current iterator points. With an InputIterator and
with all const_iterators, the return type of this overloaded
operator should be Type const &. This operator returns a
reference to a string. This string is obtained by dereferencing
the dereferenced d_current value. As d_current is an
iterator to string * elements, two dereference operations are
required to reach the string itself:
inline std::string &StringPtr::iterator::operator*() const
{
return **d_current;
}
iterator const operator+(int stepsize) const; this operator
advances the current iterator by stepsize steps:
inline StringPtr::iterator const
StringPtr::iterator::operator+(int step) const
{
return iterator(d_current + step);
}
iterator const operator-(int stepsize) const; this operator
decreases the current iterator by stepsize steps:
inline StringPtr::iterator const
StringPtr::iterator::operator-(int step) const
{
return iterator(d_current - step);
}
iterator(iterator const &other); iterators may be constructed
from existing iterators. This constructor doesn't have to be
implemented, as the default copy constructor can be used.
std::string *operator->() const is an additionally added
operator. Here only one dereference operation is required,
returning a pointer to the string, allowing us to access the
members of a string via its pointer.
inline std::string *StringPtr::iterator::operator->() const
{
return *d_current;
}
operator+= and
operator-=. They are not formally required by
RandomAccessIterators, but they come in handy anyway:
inline StringPtr::iterator &StringPtr::iterator::operator+=(int step)
{
d_current += step;
return *this;
}
inline StringPtr::iterator &StringPtr::iterator::operator-=(int step)
{
d_current -= step;
return *this;
}
operator+(int step) can be omitted from the interface. Of course, the tag
to use would then be std::forward_iterator_tag. The tags (and the set of
required operators) varies accordingly for the other iterator types.
std::iterator a
std::reverse_iterator
exists, that nicely implements the reverse iterator for us once we
have defined an iterator class. Its constructor merely requires an object of
the iterator type for which we want to construct a reverse iterator.
To implement a reverse iterator for StringPtr we only need to define
the reverse_iterator type in its interface. This requires us to specify
only one line of code, which must be inserted after the interface of the class
iterator:
typedef std::reverse_iterator<iterator> reverse_iterator;
Also, the well known members
rbegin and
rend are added to
StringPtr's interface. Again, they can easily be implemented inline:
inline StringPtr::reverse_iterator StringPtr::rbegin()
{
return reverse_iterator(end());
}
inline StringPtr::reverse_iterator StringPtr::rend()
{
return reverse_iterator(begin());
}
Note the arguments the reverse_iterator constructors receive: the
begin point of the reversed iterator is obtained by providing
reverse_iterator's constructor with the value returned by the member
end: the endpoint of the normal iterator range; the endpoint of
the reversed iterator is obtained by providing reverse_iterator's
constructor with the value returned by the member begin: the begin
point of the normal iterator range.
The following small program illustrates the use of StringPtr's
RandomAccessIterator:
#include <iostream>
#include <algorithm>
#include "stringptr.h"
using namespace std;
int main(int argc, char **argv)
{
StringPtr sp;
while (*argv)
sp.push_back(new string(*argv++));
sort(sp.begin(), sp.end());
copy(sp.begin(), sp.end(), ostream_iterator<string>(cout, " "));
cout << "\n======\n";
sort(sp.rbegin(), sp.rend());
copy(sp.begin(), sp.end(), ostream_iterator<string>(cout, " "));
cout << '\n';
}
/*
when called as:
a.out bravo mike charlie zulu quebec
generated output:
a.out bravo charlie mike quebec zulu
======
zulu quebec mike charlie bravo a.out
*/
Although it is thus possible to construct a reverse iterator from a normal
iterator, the opposite does not hold true: it is not possible to
initialize a normal iterator from a reverse iterator.
Assume we would like to process all lines stored in vector<string>
lines up to any trailing empty lines (or lines only containing blanks) it
might contain. How should we proceed? One approach is to start the processing
from the first line in the vector, continuing until the first of the trailing
empty lines. However, once we encounter an empty line it does of course not
have to be the first line of the set of trailing empty lines. In that case,
we'd better use the following algorithm:
rit = find_if(lines.rbegin(), lines.rend(), NonEmpty());
to obtain a reverse_iterator rit pointing to the last non-empty
line.
for_each(lines.begin(), --rit, Process());
to process all lines up to the first empty line.
reverse_iterator? The solution is not very difficult, as an iterator may
be initialized from a pointer. Although the reverse iterator rit is not a
pointer, &*(rit - 1) or &*--rit is. So we use
for_each(lines.begin(), &*--rit, Process());
to process all the lines up to the first of the set of trailing empty
lines. In general, if rit is a reverse_iterator pointing to some
element and we need an iterator to point to that element, we may use
&*rit to initialize the iterator. Here, the dereference operator is
applied to reach the element the reverse iterator refers to. Then the address
operator is applied to obtain its address with which we can initialize the
iterator.