Chapter 11. Collection Class Templates

This chapter contains the following sections:

Introduction

As a developer, you no doubt periodically ask yourself, "Haven't I coded this before?" Clearly, one of the primary attractions of the C++ language is the promise of reuse, the lure of avoiding rewrites of the same old code, over and over again. The Tools.h++ collection classes take advantage of several C++ language features that support reuse.

The Smalltalk-like collection classes, discussed later in detail, effect object-code reuse through polymorphism and inheritance. In this chapter, we demonstrate reuse in the Tools.h++ collection class templates, called templates in our jargon.

A template is a class declaration parameterized on a type: a prescription for how a particular type of collection class should behave. For example, a Vector template would describe such things as how to index an element, how long it is, how to resize it, and so on. The actual type of the elements is independent of these larger, more general issues.

With templates, you achieve extreme source-code reuse by writing code for your collections without regard to the particular type or types of elements being collected. The template mechanism allows you to represent these types using formal template parameters, which act as place holders. Later, when you want to make use of your collection class template, you instantiate the template with an actual type. At that point, the compiler goes back to the class template and fills it in with that type, as if you had written it that way in the first place.

Without templates, you would have to write class VectorOfInt, VectorOfDouble, VectorOfFoo, and so on; with templates, you simply code one class, Vector<T> , where T can stand for any type. From there, you're free to create Vector<int>, Vector<double>, Vector<Foo>, or a vector of some type never conceived of when the class template was written originally.

Template Overview

To gain some perspective, let's begin with a general example that shows how templates work. We'll explain concepts from the example throughout the section, though you'll probably follow this without difficulty now:

#include <iostream.h>
#include <rw/cstring.h>
#include <rw/regexp.h>
#include <rw/tvdlist.h>
 
int main()
{
  // Declare a linked-list of strings:
  RWTValDlist<RWCString> stringList;
  RWTValDlist<RWCString>::iterator iter;
  RWCString sentence;
 
  // Add words to the list:
  stringList.insert("Templates");
  stringList.insert("are");
  stringList.insert("fun");
 
  // Now use standard iterators to build a sentence:
  iter = stringList.begin();
  while (iter != stringList.end()) {
    sentence += (*iter++ + " ");
  }
  // Replace trailing blank with some oomph!
  sentence(RWCRegexp(" $")) = "!"
  
  // Display the result:
  cout << sentence << endl;
  return 0;
}

Output:

  Templates are fun!

The preceding example demonstrates the basic operation of templates. Using the collection class template RWTValDList, we instantiate the object stringList, simply by specifying type RWCString. The template gives us complete flexibility in specifying the type of the list; we don't write code for the object, and Tools.h++ doesn't complicate its design with a separate class RWTValDListofRWCString. Without the template, we would be limited to types provided by the program, or forced to write the code ourselves.

Template Naming Convention

You'll notice that the collection class template RWTValDlist in the example follows a unique format. In Tools.h++, all templates have class names starting with RWT , for Rogue Wave Template, followed by a three-letter code:

Isv

Intrusive lists

 

Val

Value-based

 

Ptr

Pointer-based

Hence, RWTValOrderedVector<T> is a value-based template for an ordered vector of type-name T. RWTPtrMultiMap<Key,T,C> is a pointer-based template based on the Standard C++ Library multimap class. Special characteristics may also modify the name, as in RWTValSortedDlist<T,C>, a value-based doubly-linked template list that automatically maintains its elements in sorted order.

Value vs. Reference Semantics in Templates

Tools.h++ collection class templates can be either value-based or pointer-based. Value-based collections use value semantics, maintaining copies of inserted objects and returning copies of retrieved objects. In contrast, pointer-based collections use reference semantics, dealing with pointers to objects as opposed to the objects themselves. See Chapter 10 for other examples of value and reference semantics.

Templates offer you a choice between value and reference semantics. In fact, in most cases, you must choose between a value-based or a pointer-based class; for example, either RWOrderedVectorVal, or RWOrderedVectorPtr.

Your choice depends on the requirements of your application. Pointer-based templates are a good choice for maximizing efficiency for large objects, or if you need to have the same group of objects referred to in several ways, requiring that each collection class point to the target objects, rather than wholly contain them.

An Important Distinction

There is a big difference between a value-based collection of pointers, and a pointer-based collection class. You can save yourself difficulty by understanding the distinction. For example, declaring:

// value-based list of RWDate pointers:
RWTValDlist<RWDate*> myBirthdayV;

gives you a value-based list, where the values are of type pointer to RWDate. The collection class will concern itself only with the pointers, never worrying about the actual RWDate objects they refer to. Now consider:

RWDate* d1 = new RWDate(29,12,55);  // December 29, 1955
myBirthdayV.insert(d1);
RWDate* d2 = new RWDate(29,12,55);  // Different object, same date
cout << myBirthdayV.occurrencesOf(d2);   // Prints 0

The above code prints 0 because the memory locations of the two date objects are different, and the collection class is comparing only the values of the pointers themselves (their addresses) when determining the number of occurrences.

Contrast that with the following:

RWTPtrDlist<RWDate> myBirthdayP;  // pointer-based list of RWDates
RWDate* d1 = new RWDate(29,12,55); // December 29,1955
myBirthdayP.insert(d1);
RWDate* d2 = new RWDate(29,12,55); // Different object, same date
cout << myBirthdayP.occurrencesOf(d2);   // Prints 1

Here the collection class is parameterized by RWDate, not RWDate*, showing that only RWDate objects, not pointers, are of interest to the list. But because it is a pointer-based collection class, communicating objects of interest is done via pointers to those objects. The collection class knows it must dereference these pointers, as well as those stored in the collection class, before comparing for equality.

Intrusive Lists in Templates

For a collection class of type-name T, intrusive lists are lists where type T inherits directly from the link type itself[13] . The results are optimal in space and time, but require you to honor the inheritance hierarchy. The disadvantage is that the inheritance hierarchy is inflexible, making it slightly more difficult to use with an existing class. For each intrusive list class, Tools.h++ offers templatized value lists as alternative non-intrusive linked lists.

Note that when you insert an item into an intrusive list, the actual item , not a copy, is inserted. Because each item carries only one link field, the same item cannot be inserted into more than one list, nor can it be inserted into the same list more than once.

Tools.h++ Templates and the Standard C++ Library

Most of the Tools.h++ collection class templates use the Standard C++ Library for their underlying implementation. The collection classes of the Standard C++ Library, called containers, act as an engine under the hood of the Tools.h++ templates.

For example, the value-based Tools.h++ double-ended queue RWTValDeque<T> has-a member of type deque<T> from the Standard C++ Library. This member serves as the implementation of the collection. Like an engine, it does the bulk of the work of adding and removing elements, and so on. RWTValDeque<T> is a wrapper class, like the hood protecting the engine. More than cosmetic, it functions as a simpler, object-oriented interface to the deque class, making it easier and more pleasant to deal with.

Thanks to inlining and the lack of any extra level of indirection, this wrapping incurs few, if any, performance penalties. If you need direct access to the implementation, the wrapper classes offer the member function std(), which returns a reference to the implementation. In addition, because the Rogue Wave template collections supply standard iterators, you can use them with the Standard C++ Library algorithms as if they were Standard C++ Library collections themselves.

Standard C++ Library Not Required

A unique feature of this version of Tools.h++ is that many of its template collections do not actually require the presence of the Standard C++ Library. Consider RWTValDlist<T> If you are using Tools.h++ on a platform that supports the Standard C++ Library, RWTValDlist<T> will be built around the Standard C++ Library list container. But if your platform does not support the Standard C++ Library, you may still use the class. Tools.h++ accomplishes this feat transparently through an alternate implementation, not based on the Standard C++ Library. The appropriate implementation is selected at compile time based on the settings in the configuration header file, rw/compiler.h. In fact, the alternate implementations are exactly those that were employed in the previous version of Tools.h++.

When using one of these template collections without the Standard C++ Library, you will be restricted to a subset of the full interface. For example, the std() member function mentioned above is not available, nor are the begin() and end() functions, which return Standard C++ Library iterators. The Tools.h++ Class Reference contains entries for both the full and the subset interfaces for all of the templates that can be used either with or without the Standard C++ Library.

There are two reasons you may want to use the restricted subset interface for a collection class template:

  1. You may be operating in an environment that does not yet support a version of the Standard C++ Library compatible with this version of Tools.h++. In that case, you have no choice but to use the restricted subset interface. The good news is that by using the interface, you will be ready to start using the full interface as soon as the Standard C++ Library becomes available on your platform.

  2. Another reason to stick to the subset interface is that you want to write portable code—a class library, perhaps—that can be deployed on multiple platforms, some without support for the Standard C++ Library. Clients of that code can still take full advantage of their individual environments; you aren't forced to inflict on them a "lowest common denominator." See "Using Templates Without the Standard Library" in this chapter for more information on the restricted subset interface.

The Standard C++ Library Containers

There are seven Standard C++ Library containers: deque, list, vector, set, multiset, map, and multimap. Tools.h++ extends these with five additional containers which are compliant with the Standard C++ Library: rw_slist, rw_hashset, rw_hashmultiset, rw_hashmap, and rw_hashmultimap. Each of these has value-based and pointer-based Rogue Wave wrapper templates. Tools.h++ also offers always-sorted versions, both value-based and pointer-based, of the doubly-linked list and vector collections. The total: 28 new or re-engineered collection class templates, all based on the Standard C++ Library!

Table 11-1. Tools.h++ Standard Library-Based Templates

Division // Notes

Value-based

Pointer-based

Standard Library Required?

Sequence-based

RWTValDlist

RWTPtrDlist

No

 

RWTValDeque

RWTPtrDeque

Yes

 

RWTValOrderedVector

RWTPtrOrderedVector

No

//External ordering, access by index

RWTValSlist

RWTPtrSlist

No

Sorted sequence-based

RWTValSortedDlist

RWTPtrSortedDlist

Yes

//Internal ordering, access by index

RWTValSortedVector

RWTPtrSortedVector

No

Associative container-based

RWTValSet

RWTPtrSet

Yes

(set-based)

RWTValMutliSet

RWTPtrMultiSet

Yes

(map-based)

RWTValMap

RWTPtrMap

Yes

//Internal ordering, access by key

RWTValMultiMap

RWTPtrMultiMap

Yes

Associative hash -based

RWTValHashSet

RWTPtrHashSet

No

(set-based)

RWTValHashMutliSet

RWTPtrHashMultiSet

No

(map-based)

RWTValHashMap

RWTPtrHashMap

No

//No ordering, access by key

RWTValHashMultiMap

RWTPtrHashMultiMap

Yes


Commonality of Interface

To keep things simple and allow you to program with more flexibility, we have implemented common interfaces within the various divisions of standard-library based collection class templates. For example, the RWTPtrSet and RWTPtrMultiSet templates have interfaces identical to their value-based cousins; so do the map-based collection classes. All of the Sequence-based collections have nearly identical interfaces within the value and pointer-based subgroups. (An exception here is the set of deque-based classes, which contain push and pop member functions designed to enhance their abstraction of the queue data structure.)

There are pluses and minuses to this approach. The downside is that it puts slightly more of the burden on you, the developer, to choose the appropriate collection class. Had we chosen not to provide the insertAt(size_type index) member function for class RWOrderedVectorVal<Type>, we could have enforced the idea that vector-based templates are not a good choice for inserting into the middle of a collection class. Instead, it is up to you to be aware of your choices and use such member functions judiciously.

On the plus side, the common interface lowers the learning curve, allows flexibility in experimenting with different collections, and provides the capability of dealing with the Rogue Wave templates polymorphically via genericity.[14]

Real-life programming is seldom as neat as the exercises in a data structures textbook. You may find yourself in a situation where it is difficult to balance the trade-offs and determine just which collection class to use. With the common interface, you can easily benchmark code that uses an RWTValDeque and later benchmark it again, substituting an RWTValOrderedVector or RWTValDlist. You can also write class and function templates that are parameterized on the collection class type. For example:

template <class RWSeqBased>
void tossHiLo(RWSeqBased& coll) {
    // throw out the high and low values:
   assert(coll.entries() >= 2);  // precondition
   coll.sort();
   coll.removeFirst();
   coll.removeLast();
}

Thanks to the common interface, the above function template will work when instantiated with any of the Rogue Wave Sequence-based templates.

Parameter Requirements

In order to use a Tools.h++ template collection class to collect objects of some type T, that type must satisfy certain minimal requirements. Unfortunately, some compilers may require the instantiating type or types to be more powerful than should be necessary.

According to the draft C++ standard, a compiler should only instantiate and compile those template functions that are actually used. Thus, if you avoid calling any member functions, such as sort(), that require valid less-than semantics, you should still be able to create a collection class of some type U for which, given instances u1 and u2, the expression (u1 < u2) is ill-formed. If your compiler does not provide this selective instantiation, you may not be able to collect objects of type U without implementing operator<(const U&, const U&) for that type.

Comparators

The associative container-based and the sorted sequence-based collection classes maintain order internally. This ordering is based on a comparison object, an instance of a comparator class you must supply when instantiating the template. A comparator must contain a const member operator(), the function-call operator, which takes two potential elements of the collection class as arguments and returns a Boolean value. The returned value should be true if the first argument must precede the second within the collection class, and false otherwise. Often, it is easiest to use one of the function-object classes provided by the Standard C++ Library in the header file <functional>. In particular, use less<T> to maintain elements in increasing order, or greater<T> to maintain them in decreasing order. For example:

#include <functional>
#include <rw/tvset.h>
#include <rw/rwdate.h>
 
RWTValSet<int, less<int> > mySet1;
RWTValSet<RWDate, greater<RWDate> > mySet2;

Here mySet1 is a set of integers kept in increasing order, while mySet2 is a set of dates held in decreasing order; that is, from the most recent to the oldest. You can use these comparators from the Standard C++ Library as long as the expression (x < y) for the case of less<T>, or (x > y) for the case of greater<T>, are valid expressions that induce a total ordering on objects of type T.

More on Total Ordering

As noted above, the comparator must induce a total ordering on the type of the items in the collection class. This means that the function-call operator of the comparator must satisfy the following two conditions[15] , assuming that comp is the comparison object and x, y, and z are potential elements of the collection class, not necessarily distinct:

I.  Exactly one of the following statements is true:
    a) comp(x,y) is true  and comp(y,x) is false
    b) comp(x,y) is false and comp(y,x) is true
    c) comp(x,y) is false and comp(y,x) is false 
      (or, in other words: not both comp(x,y) and comp(y,x) 
       are true)
II. If comp(x,y) and comp(y,z) are true, then so is 
    comp(x,z) (transitivity).

The truth of I.a implies that x must precede y within the collection class, while I.b says that y must precede x. More interesting is I.c. If this statement is true, we say that x and y are equivalent, and it doesn't matter in what order they occur within the collection class. This is the notion of equality that prevails for the templates that take a comparator as a parameter. For example, when the member function contains(T item) of an associative container-based template tests to see if the collection class contains an element equivalent to item, it is really looking for an element x in the collection class such that comp(x,item) and comp(item,x) are both false. It is important to realize that the == operator is not used. Don't worry if at first it seems counter-intuitive that so much negativity can give rise to equivalence—you are not alone! You'll soon be comfortable with this flexible way of ensuring that everything has its proper place within the collection class.

Comparators are generally quite simple in their implementation. Take for example:

class IncreasingLength {
public:
    bool operator()(const RWCString& x, const RWCString& y) 
    { return x.length() < y.length(); }
};
RWTValSet<RWCString,IncreasingLength> mySet;

Here mySet maintains a collection of strings, ordered from shortest to longest by the length of those strings. You can verify that an instance of the comparator satisfies the given requirements for total ordering. In the next example, mySet2 maintains a collection class of integers in decreasing order:

class DecreasingInt {
public:
    bool operator()(int x, int y) 
    { return x > y; }
};
RWTValSet<int, DecreasingInt> mySet2;

Although the sense of the comparison may seem backwards when you first look at it, the comparator says that x should precede y within the collection class if x is greater than y; hence, you have a decreasing sequence. Finally, let's look at a bad comparator:

// DON'T DO THIS:
class BadCompare {
public:
    bool operator()(int x, int y) 
   { return x <= y; }    // OH-OH! Not a total ordering relation
};
RWSetVal<int, BadCompare> mySet3;  // ILLEGAL COMPARATOR!

To determine why it's bad, consider an instance badcomp of BadCompare. Note that when using the value 7 for both x and y, none of the three statements I.a, I.b, or I.c is true, which violates the first rule of a total ordering relation.[16]

Hash Functors and Equalitors

The associative hash-based templates use a hash function object to determine how to place and locate objects within the collection class. An advantage of using hash function objects is efficient, constant-time retrieval of objects. A disadvantage is that objects are maintained in an order determined by mapping the result of the hash function onto the physical layout of the collection class itself. Rarely does this ordering have a useful meaning beyond the internal machinations of the hash-based container.

To avoid complete chaos, associative hash-based containers make use of an equality object. Collections which allow multiple keys that are equivalent to one another use the equality object to ensure that equivalent objects are grouped together when iteration through the container occurs. Hash collections which do not allow multiple keys use the equality object to ensure that only unique items are admitted. To effect these procedures, we need two template arguments in addition to the type or types of the elements being collected: a hash functor, and an equalitor.

A hash functor is a class or struct that contains a function-call operator that takes a const reference to a potential element of the collection class, and returns an unsigned long hash value. An equalitor is a class or struct that contains a function-call operator that takes two potential elements of the collection class, and returns true if the elements should be considered equivalent for the purpose of grouping objects or ensuring uniqueness within the collection class. For example:

#include <rw/tvhset.h>  // contains RWTValHashSet
#include <rw/cstring.h>  // Contains RWCString
struct StringHash {
      unsigned long operator()(const RWCString& s)
      {  return s.hash(); }
};
struct StringEqual {
   unsigned long operator()(const RWCString& s, const RWCString& t)
      {  return s == t; }
};
RWTValHashSet<RWCString, StringHash, StringEqual> rwhset;

Here we instantiate an RWHashValSet of RWCStrings with our own hash functor and equalitor. The example shows the relative ease of creating these structs for use as template arguments.

Iterators

Tools.h++ provides several distinct methods for iterating over a collection class. Most collections offer an apply member function, which applies your supplied function to every element of a collection class before returning. Another form of iteration is provided by separate collaborating iterator classes associated with many of the collections. For example, an RWTPtrDlistIterator<T> can be used to visit each element of an RWTPtrDlist<T> in turn. For more information on iterators, see "Iterators in Collection Classes" in Chapter 10.

Standard C++ Library Iterators

All Tools.h++ standard library-based collection class templates provide standard iterators. These iterators are fully compliant with the Standard C++ Library requirements for iterators, making them a powerful tool for using the classes in conjunction with the Standard C++ Library—especially the algorithms. Although full treatment of iterators is beyond the scope of this guide, your Standard C++ Library reference and tutorials will provide ample information.

The standard library-based collection class templates provide three types of iterators: forward, bi-directional, and random-access. Forward iterators allow unidirectional traversal from beginning to end. As suggested by the name, bidirectional iterators allow traversal in both directions—front to back, and back to front. Random-access iterators are bidirectional as well, and further distinguished by their ability to advance over an arbitrary number of elements in constant time. All of these iterators allow access to the item at the current position via the dereference operator *.

Given iterator iter and an integral value n, the following basic operations are just some of those supported:

Table 11-2. Supported Operations

Expression

Meaning

Supported by:

++iter;

advance to next item and return

Forw, Bidir, Random

iter++;

advance to next item, return original value

Forw, Bidir, Random

*iter;

return reference to item at current position

Forw, Bidir, Random

--iter;

retreat to previous item and return

Bidir, Random

iter--;

retreat to previous item, return original value

Bidir, Random

iter+=n;

advance n items and return

Random

iter-=n;

retreat n items and return

Random

Again, your standard library documentation will describe all the operators and functions available for each type of iterator.

In addition to the iterators just described, the standard library-based collection class templates also provide two typedefs used to iterate over the items in a collection class: iterator, and const_iterator. You can use the iterator typedef to traverse a collection class and modify the elements within. You can use instances of const_iterator to traverse, but not modify, the collection class and access elements. For the associative container-based and sorted sequence-based collections, which do not allow modification of elements once they are in the collection class, the iterator and const_iterator types are the same.

Finally, the templates also provide two member functions that return actual iterators you can use for traversing their respective collection classes. These member functions are begin() and end(). Each of these member functions is overloaded by a const receiver so that the non-const version returns an instance of type iterator, and the const version returns an instance of type const_iterator.

Member function begin() always returns an iterator already positioned at the first item in the collection class. Member function end() returns an iterator which has a past-the-end value, the way a pointer to the NULL character of a null-terminated character string has a value that points "past the end." An iterator of past-the-end value can be used to compare with another iterator to see if you've finished visiting all the elements in the collection class. It can also be used as a starting point for moving backwards through collection classes that provide either bidirectional or random-access iterators. The one thing you cannot do with an end() iterator is dereference it. The one thing you cannot do with an end() iterator is deference it. Here's an example using iterators to move through a list and search for a match:

RWTValDlist<int> intCollection; // a list of integers
 
// ... < put stuff in the list >
 
// position iter at start: 
RWTValDlist<int>::iterator iter = intCollection.begin();
 
// set another iterator past the end:
RWTValDlist<int>::iterator theEnd = intCollection.end();
 
// iterate through, looking for a 7:
while (iter != theEnd) {       // test for end of collection
  if (*iter == 7)              // use `*' to access current element
    return true;               // found a 7
  ++iter;                      // not a 7, try next element
}
return false;                  // never found a 7

Map-Based Iteration and Pairs

In the case of a map-based collection class, like RWMapVal<K,T,Compare>, iterators refer to instances of the Standard C++ Library structure pair<const K, T>. As you iterate over a map-based collection, you have access to both the key and its associated data at each step along the traversal. The pair structure provides members first and second, which allow you to individually access the key and its data, respectively. For example:

typedef RWTValMap<RWCString, RWDate, less<RWCString> > Datebook;
 
Datebook birthdays;
// ... < populate birthdays collection >
 
Datebook::iterator iter = birthdays.begin();
 
while (iter != birthdays.end()) {
  cout << (*iter).first              // the key
       << " was born on "
       << (*iter).second             // the data for that key
       << endl;
 
  ++iter;
};

Note that given a non-const reference to such a pair, you can still modify only the second element—the data associated with the key. This is because the first element is declared to be of type const K. Because the placement of objects within the collection class is maintained internally, based on the value of the key, declaring it as const protects the integrity of the collection class. In order to change a key within a map, you will have to remove the key and its data entirely, and replace them with a new entry.

Iterators as Generalized Pointers

It may not be obvious at first, but you can think of an iterator as a generalized pointer. Imagine a pointer to an array of ints. The array itself is a collection class, and a pointer to an element of that array is a random-access iterator. To advance to the next element, you simply use the unary operator ++. To move back to a previous element, you use --. To access the element at the current position of the iterator, you use the unary operator *. Finally, it is important to know when you have visited all the elements. C++ guarantees that you can always point to the first address past the end of an allocated array. For example:

int intCollection[10];                // an array of integers
 
// ... < put stuff in the array >
 
// position iter at start: 
int* iter = intCollection;
 
// set another iterator past the end:
int* theEnd = intCollection + 10;
 
// iterate through, looking for a 7:
while (iter != theEnd) {       // test for end of array
  if (*iter == 7)              // use `*' to access current element
    return true;               // found a 7
  ++iter;                      // not a 7, try next element
}
return false;                  // never found a 7

If you compare this code fragment to the one using standard iterators in Section 11.7.1XX, you can see the similarities. If you need a bit of help imagining how the standard iterators work, you can always picture them as generalized pointers.

Iterators and the std() Gateway

The Tools.h++ templates are meant to enhance the Standard C++ Library, not to stand as a barrier to it. The iterators described in the previous section ("Iterators") are standard iterators, and you can use them in conjunction with any components offering a standard iterator-based interface. In particular, you can use all of the standard algorithms with the Rogue Wave standard library-based collections. For example:

RWTValOrderedVector<int> vec;
 
// ... < put stuff in vector >
 
// Set the first 5 elements to 0:
fill(vec.begin(), vec.begin() + 5, 0);

In addition, you are always free to access, and in some cases to manipulate, the underlying Standard C++ Library collection class. This is accomplished via the std() member function, which returns a reference to the implementation.

The Best of Both Worlds

The following example is a complete program that creates a deck of cards and shuffles it. The purpose of the example is to show how the Tools.h++ template collections can be used in conjunction with the Standard C++ Library. See your Standard C++ Library documentation for more information on the features used in the example.

/* Note: This example requires the C++ Standard Library */
 
#include <iostream.h>
#include <algorithm>
#include <rw/tvordvec.h>
 
struct Card {
  char  rank;
  char  suit;
 
  bool operator==(const Card& c) const
    { return rank == c.rank && suit == c.suit; }
 
  Card() { }
  Card(char r, char s) : rank(r), suit(s) { }
 
  // print card: e.g. '3-C' = three of clubs, 'A-S' = ace of spades
  friend ostream& operator<<(ostream& ostr, const Card& c)
  { return (ostr << c.rank << "-" << c.suit << "  "); }
};
 
/*
 * A generator class - return Cards in sequence
 */
class DeckGen {
  int rankIdx;  // indexes into static arrays below
  int suitIdx;
  static const char Ranks[13];
  static const char Suits[4];
public:
  DeckGen() : rankIdx(-1), suitIdx(-1) { }
 
  // generate the next Card
  Card operator()() {
    rankIdx = (rankIdx + 1) % 13;
    if (rankIdx == 0)
    // cycled through ranks, move on to next suit:
      suitIdx = (suitIdx + 1) % 4;
    return Card(Ranks[rankIdx], Suits[suitIdx]);
  }
};
 
const char DeckGen::Suits[4]  = {'S', 'H', 'D', 'C' };
const char DeckGen::Ranks[13] = {'A', '2', '3', '4',
                                 '5', '6', '7', '8',
                                 '9', 'T', 'J', 'Q', 'K' };
 
int main(){
  // Tools.h++ collection:
  RWTValOrderedVector<Card> deck;
  RWTValOrderedVector<Card>::size_type pos;
 
  Card aceOfSpades('A','S');
  Card firstCard;
 
  // Use standard library algorithm to generate deck:
  generate_n(back_inserter(deck.std()), 52, DeckGen());
  cout << endl << "The deck has been created" << endl;
 
  // Use Tools.h++ member function to find card:
  pos = deck.index(aceOfSpades);
  cout << "The Ace of Spades is at position " << pos+1 << endl;
 
  // Use standard library algorithm to shuffle deck:
  random_shuffle(deck.begin(), deck.end());
  cout << endl << "The deck has been shuffled" << endl;
 
  // Use Tools.h++ member functions:
  pos = deck.index(aceOfSpades);
  firstCard = deck.first();
 
  cout << "Now the Ace of Spades is at position " << pos+1
       << endl << "and the first card is " << firstCard << endl;
}
 
/* Output (will vary because of the shuffle):
 
The deck has been created
The Ace of Spades is at position 1
 
The deck has been shuffled
Now the Ace of Spades is at position 37
and the first card is Q-D  
 
*/

Using Templates Without the Standard Library

Several of the Tools.h++ templates, such as RWTValVector<T>, RWTPtrVector<T>, RWTIsvSlist<T>, and RWTIsvDlist<T>, are not based on the Standard C++ Library. You can use them on any of our certified platforms. Also, as mentioned previously in the Introduction, you can use many of the so-called standard library-based templates without the Standard C++ Library, as long as you keep to a subset of the full interface.

Keeping the Standard C++ Library in Mind for Portability

The restricted subset interfaces are almost fully upward compatible with their corresponding standard library-based interfaces. The major difference you will find is that some collections take a different number of template parameters, depending on which underlying implementation they are using. For example, when RWTPtrHashSet is used with the Standard C++ Library, it takes three template arguments as described in "Hash Functors and Equalitors" above. However, when that same class is used without the Standard C++ Library, the restricted interface calls for only one template parameter, namely the type of item being contained. To help you write portable code that works with or without the Standard C++ Library, Tools.h++ provides two macros:

  1. Use the first macro, RWDefHArgs(T), standing for Rogue Wave Default Hash Arguments, for the hash-based template collections. For example, by declaring:

    RWTPtrHashSet<int  RWDefHArgs(int)> hset;

    you declare a hash-based set that will have the same semantics whether or not the Standard C++ Library is present. Note that you should not use a comma between the element type and the macro. Without the Standard C++ Library, the macro expands to nothing and it is as if you had declared:

    RWTPtrHashSet<int> hset;

    However, as soon as the Standard C++ Library becomes available, the macro expands as follows:

    RWTPtrHashSet<int ,RWTHasher<int>, equal_to<int> > hset;

    This declaration satisfies the full requirement of the standard library-based interface for all three parameters, and keeps the semantics consistent with the alternative non standard-library based implementation.

  2. The second macro, RWDefCArgs(T), is similar to the first. Standing for Rogue Wave Default Comparison Arguments, RWDefCArgs(T)is available for use with RWTPtrSortedVector and RWTValSortedVector. For example:

    RWTValSortedVector<int  RWDefCArgs(int)> srtvec;

    is a portable declaration that will work with or without the Standard C++ Library. Again, do not use a comma to separate the element type from the macro.

An Example

Let's start with a simple example that uses RWTValVector<T>, one of the classes that is not based on the Standard C++ Library.

#include <rw/tvvector.h>             // 1
 
main() {
    RWTValVector<double> vec(20, 0.0);                     // 2
 
    int i;
    for (i=0; i<10; i++)     vec[i] = 1.0;                 // 3
    for (i=11; i<20; i++)    vec(i) = 2.0;                 // 4
 
    vec.reshape(30);                                       // 5
    for (i=21; i<30; i++)    vec[i] = 3.0;                 // 6
    return 0;
    }

Each program line is detailed below.

//1 

This is where the template for RWTValVector<T> is defined.

//2 

A vector of doubles, 20 elements long and initialized to 0.0, is declared and defined.

//3 

The first 10 elements of the vector are set to 1.0. Here, RWValVector<double>::operator[](int) has been used. This operator always performs a bounds check on its argument.

//4 

The next 10 elements of the vector are set to 2.0. In this case, RWValVector<double>::operator()(int) has been used. This operator generally does not perform a bounds check.

//5 

Member function reshape(int) changes the length of the vector.

//6 

Finally, the last 10 elements are initialized to 3.0.

Another Example

The second example involves a hashing dictionary. By using the macro RWDefHArgs(T) when you declare the hashing dictionary, you insure that your code is portable with or without access to the Standard C++ Library.

#include <rw/cstring.h>
#include <rw/rstream.h>
#include <iomanip.h>
 
class Count {                                                  // 1
  int  N;
public:                                                        // 2
  int  operator++()  { return ++N; }                           // 3
  operator    int()  { return N; }                             // 4
};
 
unsigned hashString ( const RWCString& str )                   // 5
  { return str.hash(); }
 
main() {
 
  RWTValHashDictionary<RWCString,
                       Count  /* Note: no comma here! */
                       RWDefHArgs(RWCString)> hmap(hashString); //6
 
 
  RWCString token;
  while ( cin >> token )                                       // 7
    ++hmap[token];                                             // 8
 
  RWTValHashDictionaryIterator<RWCString,Count> next(hmap);    // 9
 
  cout.setf(ios::left, ios::adjustfield);                     // 10
  while ( ++next )                                            // 11
    cout << setw(20) << next.key()
         << " " << setw(10) << next.value() << endl;          // 12
 
  return 0;
}

Program Input:

How much wood could a woodchuck chuck if a woodchuck could chuck wood ?

Program Output:

much                 1
wood                 2
a                    2
if                   1
woodchuck            2
could                2
chuck                2
How                  1
?                    1

In the code above, the problem is to read an input file, break it up into tokens separated by white space, count the number of occurrences of each token, and then print the results. The general approach is to use a dictionary to map each token to its respective count. Here's a line-by-line description:

//1 

This is a class used as the value part of the dictionary.

//2 

A default constructor is supplied that zeros out the count.

//3 

We supply a prefix increment operator. This will be used to increment the count in a convenient and pleasant way.

//4 

A conversion operator is supplied that allows Count to be converted to an int. This will be used to print the results. Alternatively, we could have supplied an overloaded operator<<() to teach a Count how to print itself, but this is easier.

//5 

This is a function that must be supplied to the dictionary constructor. Its job is to return a hash value given an argument of the type of the key. Note that Tools.h++ supplies static hash member functions for classes RWCString, RWDate, RWTime, and RWWString that can be used in place of a user-supplied function. To keep the example general, we chose a user-defined function rather than one of the static hash member functions defined by Tools.h++.

//6 

Here the dictionary is constructed. Given a key, the dictionary can be used to look up a value. In this case, the key will be of type RWCString, the value of type Count. The constructor requires a single argument: a pointer to a function that will return a hash value, given a key. This function was defined on line 5 above. Note that we used the RWDefHArgs(T) macro to ensure that the program will be portable among platforms with and without the Standard C++ Library.

//7 

Tokens are read from the input stream into an RWCString. This will continue until an EOF is encountered. How does this work? The expression cin >> token reads a single token and returns an ostream&. Class ostream has a type conversion operator to void*, which is what the while loop will actually be testing. Operator void* returns this if the stream state is "good", and zero otherwise. Because an EOF causes the stream state to turn to "not good", the while loop will be broken when an EOF is encountered. See the RWCString entry in the Class Reference, and the ios entry in the class reference guide that comes with your compiler.

//8 

Here's where all the magic occurs. Object map is the dictionary. It has an overloaded operator[] that takes an argument of the type of the key, and returns a reference to its associated value. Recall that the type of the value is a Count. Hence, map[token] will be of type Count. As we saw on line 3, Count has an overloaded prefix increment operator. This is invoked on the Count, thereby increasing its value.

What if the key isn't in the dictionary? Then the overloaded operator[] will insert it, along with a brand new value built using the default constructor of the value's class. This was defined on line 2 to initialize the count to zero.

//9 

Now it comes time to print the results. We start by defining an iterator that will sweep over the dictionary, returning each key and value.

//10 

The field width of the output stream is adjusted to make things pretty.

//11 

The iterator is advanced until it reaches the end of the collection class. For all template iterators, the prefix increment operator advances the iterator, then tests whether it has gone past the end of the collection class.

//12 

The key and value at the position of the iterator are printed.

Migration Guide: For Users of Previous Versions of Tools.h++

As we explained in the introduction to this manual, one of our primary goals for this version of Tools.h++ is to protect your investment in existing code based on previous versions of the library. As you can see from this chapter, we have significantly re-engineered the collection class templates in order to bring them up to date with the Standard C++ Library. The following classes were re-engineered:

RWTPtrDlist

 

RWTValDlist

 

RWTPtrHashDictionary

 

RWTValHashDictionary

 

RWTPtrHashSet

 

RWTValHashSet

 

RWTPtrHashTable

 

RWTValHashTable

 

RWTPtrOrderedVector

 

RWTValOrderedVector

 

RWTPtrSlist

 

RWTValSlist

 

RWTPtrSortedVector

 

RWTValSortedVector

You have seen that you can now use all of these classes either with or without the Standard C++ Library. Used without the Standard C++ Library, they have the same interfaces and implementations as in the previous version of Tools.h++, updated with some bug fixes. These minor enhancements should not cause any source incompatibilities with existing code.

You may need to make a few changes to existing source code when using the above classes with the Standard C++ Library. The adjustments required for specific classes are outlined below.

  • Extra template arguments are now required for:

    RWTPtrHashDictionary

     

    RW

    TValHashDictionary

    RWTPtrHashSet

     

    RW

    TValHashSet

    RWTPtrHashTable

     

    RW

    TValHashTable

    RWTPtrSortedVector

     

    RW

    TValSortedVector


Existing code using these templates will not provide the number of template arguments expected by this version of Tools.h++ when used with the Standard C++ Library. The solution to this problem is to use the macros discussed in "Using Templates Without the Standard Library". Using the macros described there will satisfy the compiler and preserve the semantics of your existing code.

  • The class hierarchy has changed for:

    RWTPtrHashSet

     

    RWTValHashSet

     

    RWTPtrHashTable

     

    RWTValHashTable

     

    RWTPtrOrderedVector

     

    RWTValOrderedVector

     

    RWTPtrSortedVector

     

    RWTValSortedVector


If you have existing code that makes use of any of the inheritance relationships among the collection class templates, that code will not compile with this version of Tools.h++ when used with the Standard C++ Library. There are no inheritance relationships among the standard library-based implementations of the collection class templates. For example, in the previous version of Tools.h++, RWTPtrHashSet inherited from RWTPtrHashTable, RWTValOrderedVector inherited from RWTValVector, and RWTValSortedVector inherited from RWTValOrderedVector. The pointer-based versions of these templates followed a similar pattern. These relationships do not hold in the new version of Tools.h++. If you have code based on this inheritance, you will need to modify it .

The most likely place where you will find this problem in your existing code is when building an RWTValHashTableIterator from an RWTValHashSet, or an RWTPtrHashTableIterator from an RWTPtrHashSet. Because the constructor for RWTValHashTableIterator is expecting a reference to an RWTValHashTable, passing in an RWTValHashSet instead depends on the inheritance relationship.

The solution to this particular problem is found in the new class RWTPtrHashSetIterator. Wherever you find code which constructs an RWTValHashTableIterator from an RWTValHashSet, use an RWTValHashSetIterator instead. Note that RWTValHashSetIterator is provided whether or not the Standard C++ Library is available, so you can modify your code now in anticipation of migrating your code to the standard library-based implementations.

  • Required less-than semantics for an element type may affect:

    RWTPtrDlist

     

    RWTValDlist

     

    RWTPtrOrderedVector

     

    RWTValOrderedVector

     

    RWTPtrSlist

     

    RWTValSlist

     

    RWTPtrSortedVector

     

    RWTValSortedVector


As mentioned above, some compilers will require that the expression (t1 < t2) be defined for two instances of your element type. This is due to the inclusion of convenient member functions, such as sort() and min_element(),combined with certain compilers that instantiate all member functions whether used or not. You might have existing code that instantiates one of these templates on a type T for which no operator<() is defined. If that is the case, you will have to define one.

The best thing would be to define it in a way you can really use, if you ever use those member functions which really do require it. The quick and dirty approach would be to globally define a dummy operator<() whose only purpose is to appease the compiler. Our experience is that code written "just to appease the compiler" constitutes a maintenance nightmare. Please avoid it if at all possible.



[13] See Stroustrup, The C++ Programming Language, Second Edition, Addison-Wesley, 1991, for a description of intrusive lists.()

[14] For a discussion of genericity versus inheritance, see Meyer (1988).

[15] Adapted from Knuth (1973).

[16] Unfortunately, the requirement for total ordering is a logical, not a semantic one, so the compiler cannot help by rejecting poorly chosen comparitors. In general, such code will compile, but probably have unexpected behavior.