In the chapter about containers (chapter ref(CONTAINERS)) it was noted that
containers own the information they contain. If they contain objects, then
those objects are destroyed once the containers are destroyed. As pointers are
not objects their use in containers is discouraged (STL's ti(unique_ptr) and
ti(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 tt(StringPtr), an ordinary class is derived from the tt(std::vector)
container that uses tt(std::string *) as its data type:
        verbinclude(-a examples/stringptrs/stringptr1.h)
    This class needs a destructor: as the object stores string pointers, a
destructor is required to destroy the strings once the tt(StringPtr) object
itself is destroyed.  Similarly, a copy constructor and an overloaded
assignment operator are 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 use the tt(sort) generic algorithm with
tt(StringPtr) objects. This algorithm (see section ref(SORT)) requires two
em(RandomAccessIterators). Although these iterators are available (via
tt(std::vector)'s tt(begin) and tt(end) members), they return iterators to
tt(std::string *)s. When comparing tt(string *) values the pointer values
instead of the content of the strings are compared, which is not what we want.

    To remedy this, an internal type tt(StringPtr::iterator) is defined,
not returning iterators to pointers, but iterators to the em(objects) these
pointers point to. Once this tt(iterator) type is available, we can add the
following members to our tt(StringPtr) class interface, hiding the identically
named, but useless members of its base class:
        verb(    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
tt(StringPtr) object can easily be sorted:
        verb(    int main()
    {
        StringPtr sp;               // assume sp is somehow filled

        sort(sp.begin(), sp.end()); // sp is now sorted
    })

To make this work, a type tt(StringPtr::iterator) is defined. As suggested by
its type name, tt(iterator) is a nested type of tt(StringPtr). To use a
tt(StringPtr::iterator) in combination with the tt(sort) generic algorithm it
must be a ti(RandomAccessIterator), whose tt(value_type) is a
tt(std::string). Therefore, the iterator specifies:
        verbinclude(//USING examples/stringptrs/stringptr.h)
    
    Now we're ready to redesign tt(StringPtr)'s class interface. It offers
members returning (reverse) iterators, and a nested tt(iterator) class. Here
is its interface:
        verbinclude(//STRINGPTR examples/stringptrs/stringptr.h)

    Next we have a look at tt(StringPtr::iterator)'s characteristics:
    itemization(
    itt(iterator) defines tt(StringPtr) as its friend, so tt(iterator)'s
constructor can be private. Only the tt(StringPtr) class itself should be able
to construct iterators. Copy construction and iterator-assignment should be
possible, but that's possible by default and needs no specific declaration or
implementation. Furthermore, since an iterator is already provided by
tt(StringPtr)'s base class, we can use that iterator to access the information
stored in the tt(StringPtr) object.

    it() tt(StringPtr::begin) and tt(StringPtr::end) may simply return
tt(iterator) objects. They are implemented like this:
        verbinclude(//BEGEND examples/stringptrs/stringptr.h)

    it() All of tt(iterator)'s remaining members are public. It's very easy to
implement them, mainly manipulating and dereferencing the available iterator
tt(d_current). A tt(RandomAccessIterator) requires a series of operators. They
usually have very simple implementations, and can often very well be
implemented inline:
        itemization(
        itt(iterator &operator++()); the pre-increment operator:
            verbinclude(//PREINC examples/stringptrs/stringptr.h)
        it() tt(iterator operator++(int)); the post-increment operator:
            verbinclude(//POSTINC examples/stringptrs/stringptr.h)
        itt(iterator &)ttNoCt(operator--()); the pre-decrement operator:
            verbinclude(//PREDEC examples/stringptrs/stringptr.h)
        it() ttNoCt(iterator operator--(int)); the post-decrement operator:
            verbinclude(//POSTDEC examples/stringptrs/stringptr.h)
        itt(iterator &operator=(iterator const &other)); the overloaded
            assignment operator. Since tt(iterator) objects do not allocate
            any memory themselves, the default assignment operator can be
            used.
        itt(bool operator==(iterator const &rhv) const); testing the equality
            of two tt(iterator) objects:
                verbinclude(//OPEQ examples/stringptrs/stringptr.h)
        itt(auto operator<=>(iterator const &rhv) const); testing the
            ordering of two tt(iterator) objects: 
                verbinclude(//OPSPACE examples/stringptrs/stringptr.h)
        itt(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):
                verbinclude(//OPSUB examples/stringptrs/stringptr.h)
        itt(Type &operator*() const); returning a reference to the object to
            which the current iterator points. With an tt(InputIterator) and
            with all tt(const_iterators), the return type of this overloaded
            operator should be tt(Type const &). This operator returns a
            reference to a string. This string is obtained by dereferencing
            the dereferenced tt(d_current) value. As tt(d_current) is an
            iterator to tt(string *) elements, two dereference operations are
            required to reach the string itself:
                verbinclude(//OP* examples/stringptrs/stringptr.h)
        itt(iterator operator+(int stepsize) const); this operator
            advances the current iterator by tt(stepsize):
                verbinclude(//OPADD examples/stringptrs/stringptr.h)
        itt(iterator operator-(int stepsize) const); this operator
            decreases the current iterator by tt(stepsize):
                verbinclude(//OP- examples/stringptrs/stringptr.h)
        itt(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.
                verbinclude(//OPARROW examples/stringptrs/stringptr.h)
        it() Two more additionally added operators are tt(operator+=) and
            tt(operator-=). They are not formally required by
            tt(RandomAccessIterators), but they come in handy anyway:
                verbinclude(//OPARITH examples/stringptrs/stringptr.h)
        )
    )
    The interfaces required for other iterator types are simpler, requiring
only a subset of the interface required by a random access iterator. E.g.,
the forward iterator is never decremented and never incremented over arbitrary
step sizes. Consequently, in that case all decrement operators and
tt(operator+(int step)) can be omitted from the interface. Of course, the tag
to use would then be tt(std::forward_iterator_tag). The tags (and the set of
required operators) vary accordingly for the other iterator types.
