Template classes -- bags and sets

Numerous C++ libraries offering data structures for containers are available. Yet, many programmers choose to develop such a library themselves. The reasons for this may vary from matters of taste to considerations with respect to the formal properties of the types offered.
  template< class T >
  class bag { 
\fbox{bag<T>}
public: bag() { s = new list<T>; } bag(const bag<T>& b) { s = b.s; } ~bag() { delete s; } bag<T>& operator=(const bag<T>& b) { s = b.s; return *this; } virtual void insert(const T& e) { s->insert(e); } operator iter<T>() const { return *s; } int count(const T& e) const; void map(T f(const T& e)); protected: list<T>* s;
see section gen-list

};

slide: The implementation of a bag

In the following, we will look at some issues in defining generic classes for the mathematical data types bag, set and powerset. Such data types must, for example, be provided in a library meant to support the use of formal methods as outlined in section 3-design. Naturally, in developing real industrial-strength libraries many more issues play a role. See, for example,  [BV90]. In slide cc-bag, a generic definition for the type bag is given. Mathematically, a bag or multi-set is a set which may contain multiple instances of a particular value. (With respect to the subtype refinement relation, however, a set may be considered as a subtype of bag.) The template class bag offers a default constructor, a copy constructor and a member function for assignment, as required by the canonical class idiom presented in section canonical. An instance of the (generic) list class, developed in section generic, is used to store the elements of the bag. The destructor of bag simply deletes the the protected data member s. To insert an element, bag::insert forwards the call to list::insert and, similarly, when an iterator is requested the list pointer is converted appropriately.
  template< class T >  
\c{\fbox{bag<T>::count}}
int bag<T>::count(const T& e) const { iter<T> it = *this; T* p = 0; int cnt = 0; while ( p = it() ) if ( (T&) e == *p ) cnt++;
$(*)

return cnt; } template< class T >
\c{\fbox{bag<T>::map}
void bag<T>::map(T f(const T& e)) { iter<T> it = *this; T* p = 0; while ( p = it() ) *p = f(*p); }

slide: Bag operations

The function bag::count and bag::map are defined in slide cc-bag-op. The count function tells how many instances of a particular element are in the bag. It employs an iterator to traverse the list and compares its contents with the element given as an argument. The function map may be used to modify the contents of the bag applying some mapping function to each element:
  int S(const int& x) { return x+1; }
  
  bag b;
  b.insert(1); b.insert(2); b.insert(1);
  b.map(S);
  iter it = b; 
get the iterator

int* p = 0;
start

while ( p = it() ) { cout << "item;" << *p << endl;
take the value

}
In the example above, the function S is defined as the successor function for integers. Also, a bag b of integers is declared, into which the (integer) elements 1,2 and 1 are inserted. As a consequence
b.count(1) will deliver 2 and b.count(2) will deliver 1 as a result. The bag::map function is used to apply the successor function S to each element of the bag. Then, an iterator is obtained, simply by assignment with an implicit conversion, and the contents of the bag are written to standard output.
  template< class T >
  class set : public bag<T> { 
\c{\fbox{set<T>}
public: void insert(const T& e) { if (!member(e)) bag<T>::insert(e); } bool member(const T& e) { return count(e) == 1; } };

slide: Deriving a set class

Evidently, the class
bag lacks many of the features required for the mathematical notion of a bag, such as operators for bag union and bag intersection. Nevertheless, bag may conveniently be used to define the class set as a derived class. The set class given in slide cc-set defines an additional function member and redefines insert to check that the element is not already a member of the set (since, in contrast to a bag, a set may not contain multiple instances of a value). The function member delivers true when the number of occurrences of an element is precisely one, otherwise it returns false.
  template< class T > 
\c{\fbox{ set<T> == set<T> }
int operator==(const set<T>& s, const set<T>& b) { iter<T> it = s; T* p = 0; int eq = 1; while ( eq && (p = it()) ) if ( s.count(*p) != b.count(*p) ) eq = false; return eq; }

slide: Equality for sets

Equality between sets may be defined as in slide cc-set-eq. Set equality amounts to element-wise correspondence, irrespective of the order in which the elements occur. The definition given in slide cc-set-eq is somewhat more general than necessary, in that it also applies to bags. The requirement
! member(*p) would have been sufficient. The definition of equality for sets is necessary in order to be able to define instances of set having a set-valued instantiation parameter, such as the class power defined in slide cc-power, which is derived from set< set >. When instantiating set< set > for some type T, equality is required for set by the line $(*) in the function bag::count defined in slide cc-bag-op.
  template< class T > 
\fbox{{\tt operator<<}}
ostream& operator<<(ostream& os, const set<T>& s) { iter<T> it = s; T* p = 0; while ( p = it() ) { cout << *p << endl; } return os; }

slide: Writing a set to a stream

In slide cc-set-op, a generic operator is defined to write an arbitrary set to a stream, by overloading {\tt operator<<} for const set&. To traverse the elements of the set it employs an iterator, obtained by assigning the set reference to an iter instance.
  template< class T >
  class power : public set<set<T> > {
\c{\fbox{power<T>}}
};

slide: The powerset class

To define the class power it suffices to derive the class from set< set > . However, according to the rules given for the canonical class idiom, both power and set should be augmented with a default and copy constructor, and an assignment operator and destructor as well. Note that the map function as defined for bag is potentially unsafe for both set and power instances. For example, the function given to map may result in identical values for different arguments. As a consequence, the restriction that the set does not contain multiple instances of the same value may be violated.
  set s1;
  s1.insert(1); s1.insert(2); 
  set s2;
  s2.insert(2); s2.insert(3);
  cout << s1;
  power b;
  b.insert(s1); b.insert(s2);
  cout << (set< set >&) b; 
\c{// cast is necessary}
An example of employing the powerset is given above. First, two instances of set are created, which are then inserted in an instance of power. The {\tt operator<<} function (defined for set in slide cc-set-op) may be used to write the powerset to standard output. For each (set-valued) element of the powerset, the {\tt operator<<} function instantiated for set is called to write the individual elements to standard output.