PART 18 Q117: How can I insert/access/change elements from a linked list/hashtable/etc? A: I'll use a `inserting into a linked list' as a prototypical example. The obvious approach is to allow insertion at the head and tail of the list, but that would produce a library that is too weak (a weak library is almost worse than no library). Whenever encapsulation frustrates rather than helps a user, it may be that the class' public interface needs enhancing. If class List only supports adding at the front and tail, it *definitely* needs more strength. This answer will be a lot to swallow for novice C++'ers, so I'll give a couple of options. As usual, the first is easiest, while the second option is better. I also give a thumbnail sketch of a third option, which has certain advantages and disadvantages over the second option. [1] Empower the List with a `viewport' or `cursor' that references an arbitrary list element. Implies adding member fns to List such as advance(), backup(), atend(), atbegin(), rewind(), fastforward(), and current(). `current()' returns the element of the List that is currently `under the cursor'. Finally you'll need to add a few more member fns to *mutate* the list, such as changeto(X), insert(X), remove(), etc. [2] Provide a separate class called ListIter. ListIter has member fns named similar to the above (though probably with operator overloading, but that's just syntactic sugar for member fns). The List itself would have none of the above mentioned member fns, and the ListIter would be a `friend' of List (ListIter would have to have access to the innards of List; this allows the world to have safe access abilities to List without violating encapsulation). The reason option [2] is better becomes apparent when you use classes that only support [1]. In particular, if you pass a List off to a subcall, you'd better hope the subcall didn't warp the `cursor' around, or your code may fail. Also, if you try to get all pairs of elements, it's very hard if you only have one cursor. The distinct-class iterator concept removes these restrictions. My own class library uses these extensively, as will most any other commercial grade class library. Note that the options are not mutually exclusive; it is possible to provide both [2] *and* [1], giving rise to the notion of a `primary' list position, with instances of ListIter being somewhat secondary. [3] The third possibility considers the entire iteration as an atomic event. A class is created which embodies this event. The nice thing about this third alternative is that the public access methods (which may be virtual fns) can be avoided during the inner loop, thus enhancing performance. The down side is that you get extra object code in the application, since templates gain speed by duplicating code. This third technique is due to Andrew Koenig in a paper published recently in JOOP [`Templates as interfaces', JOOP, 4, 5 (Sept 91)]. You can also see a taste of it in Bjarne Stroustrup's book, The C++ Programming Language Second Edition (look for `Comparator' in the index). Q118: What's the idea behind `templates'? A: A template is a cookie-cutter that specifies how to cut cookies that all look pretty much the same (although the cookies can be made of various kinds of dough, they'll all have the same basic shape). In the same way, a class template is a cookie cutter to description of how to build classes that all look basically the same, and a function template describes how to build similar looking functions. The questions about templates are in the `Containers' section since templates are often used to build type safe containers (although this only scratches the surface for how they can be used). We will see how to use templates to build container classes below. Q119: What's the syntax / semantics for a `function template'? A: Consider this function that swaps its two integer arguments: void swap(int& x, int& y) { int tmp = x; x = y; y = tmp; } If we also had to swap floats, longs, Strings, Sets, and FileSystems, we'd get pretty tired of coding lines that look almost identical except for the type. Mindless repetition is an ideal job for a computer, hence a function template: templatevoid swap(T& x, T& y) { T tmp = x; x = y; y = tmp; } Every time we used `swap()' with a given pair of types, the compiler will go to the above definition and will create yet another `template function' as an instantiation of the above. Ex: main() { int i,j; /*...*/ swap(i,j); //instantiates a swap for `int' float a,b; /*...*/ swap(a,b); //instantiates a swap for `float' char c,d; /*...*/ swap(c,d); //instantiates a swap for `char' String s,t; /*...*/ swap(s,t); //instantiates a swap for `String' } (note: a `template function' is the instantiation of a `function template'). Q120: What's the syntax / semantics for a `class template'? A: Consider this container class of that acts like an array of integers: //this would go into a header file such as `Vec.h': class Vec { int xlen; int* xdata; int check(int i); //return i if i>=0 && i = xlen) throw BoundsViol("Vec", i, xlen); return i; } Just as with `swap()' above, repeating the above over and over for Vec of float, char, String, Vec, Vec-of-Vec-of-Vec, etc, will become tedious. Hence we create a single class template: //this would go into a header file such as `Vec.h': template class Vec { int xlen; T* xdata; int check(int i); //return i if i>=0 && i int Vec ::check(int i) { if (i < 0 || i >= xlen) throw BoundsViol("Vec", i, xlen); return i; } Unlike template functions, template classes (instantiations of class templates) need to be explicit about the parameters over which they are instantiating: main() { Vec vi; Vec vf; Vec vc; Vec vs; Vec< Vec > vv; } // ^^^-- note the space; do NOT use Vec > since the // `maximal munch' rule would grab a single `>>' token Q121: What is a `parameterized type'? A: A parameterized type is a type that is parameterized over another value or type. Ex: List is a type that is parameterized over another type, `int'. Therefore the C++ rendition of parameterized types is provided by class templates. Q122: What is `genericity'? A: Not to be confused with `generality' (which just means avoiding solutions which are overly specific), `genericity' means parameterized types. In C++, this is provided by class templates. Q123: How can I fake templates if I don't have a compiler that supports them? A: The best answer is: buy a compiler that supports templates. When this is not feasible, the next best answer is to buy or build a template preprocessor (ex: reads C++-with-templates, outputs C++-with-expanded-template-classes; such a system needn't be perfect; it cost my company about three man-weeks to develop such a preprocessor). If neither of these is feasible, you can use the macro preprocessor to fake templates. But beware: it's tedious; templates are a better solution wrt development and maintenance costs. Here's how you'd declare my `Vec' example from above. First we define `Vec(T)' to concatenate the name `Vec' with that of the type T (ex: Vec(String) becomes `VecString'). This would go into a header file such as Vec.h: #include //to get the `name2()' macro #define Vec(T) name2(Vec,T) Next we declare the class Vec(T) using the name `Vecdeclare(T)' (in general you would postfix the name of the class with `declare', such as `Listdeclare' etc): #define Vecdeclare(T) \ class Vec(T) { \ int xlen; \ T* xdata; \ int check(int i); /*return i if in bounds else throw*/ \ public: \ int len() const { return xlen; } \ const T& operator[](int i) const { xdata[check(i)]; } \ T& operator[](int i) { xdata[check(i)]; } \ Vec(T)(int L=10): xlen(L), xdata(new T[L]) {/*...*/}\ ~Vec(T)() { delete [] xdata; } \ }; Note how each occurrence of `Vec' has the `(T)' postfixed. Finally we set up another macro that `implements' the non-inline member function(s) of Vec: //strangely enough this can also go into Vec.h #define Vecimplement(T) \ int Vec(T)::check(int i) \ { \ if (i < 0 || i >= xlen) throw BoundsViol("Vec", i, xlen); \ return i; \ } When you wish to use a Vec-of-String and Vec-of-int, you would say: #include "Vec.h" //pulls in too; see below... declare(Vec,String) //`declare()' is a macro defined in declare(Vec,int) Vec(String) vs; //Vec(String) becomes the single token `VecString' Vec(int) vi; In exactly one source file in the system, you must provide implementations for the non-inlined member functions: #include "Vec.h" declare (Vec,String) declare (Vec,int) declare (Vec,float) implement(Vec,String) implement(Vec,int) implement(Vec,float) Note that types whose names are other than a single identifier do not work properly. Ex: Vec(char*) creates a class whose name is `Vecchar*'. The patch is to create a typedef for the appropriate type: #include "Vec.h" typedef char* charP; declare(Vec,charP) It is important that every declaration of what amounts to `Vec ' must all use exactly the same typedef, otherwise you will end up with several equivalent classes, and you'll have unnecessary code duplication. This is the sort of tedium which a template mechanism can handle for you.