Circulators are quite similar to iterators that are described in Chapter 4. Circulators are a generalization of pointers that allow a programmer to work with different circular data structures like a ring list in a uniform manner. Please note that circulators are not part of the STL, but of CGAL. A summary of the requirements for circulators is presented here. Thereafter, a couple of adaptors are described that convert between iterators and circulators. For the complete description of the requirements, support to develop own circulators, and the adaptors please refer to the CGAL Reference Manual: Part 3: Support Library.
The specialization on circular data structures gives the reason for the slightly different requirements for circulators than for iterators. A circular data structure has no natural past-the-end value. In consequence, a container supporting circulators will not have an end()-member function, only a begin()-member function. The semantic of a range is different for a circulator : The range denotes the sequence of all elements in the data structure. For iterators, this range would be empty. A separate test for an empty sequence has been added to the requirements. A comparison NULL for a circulator tests whether the data structure is empty or not. As for C++, we recommend the use of 0 instead of NULL. An example function demonstrates a typical use of circulators. It counts the number of elements in the range :
template <class Circulator, class Size> void count( Circulator c, Circulator d, Size& n) { n = 0; if ( c != 0) { do { ++n; } while (++c != d); } }
Given a circular data structure , the expression count(.begin(), .begin(), counter) returns the number of elements of in the counter parameter.
As for iterators, circulators come in different flavors. There are forward, bidirectional and random access circulators. They are either mutable or constant. The past-the-end value is not applicable for circulators.
Reachability: A circulator is called reachable from a circulator if and only if there is a finite sequence of applications of operator++ to that makes . If and refer to the same non-empty data structure, then is reachable from , and is reachable from . In particular, any circulator referring to a non-empty data structure will return to itself after a finite sequence of applications of operator++ to .
Range: Most of the library's algorithmic templates that operate on data structures have interfaces that use ranges. A range is a pair of circulators that designate the beginning and end of the computation. A range is a full range; in general, a range refers to the elements in the data structure starting with the one pointed to by and up to but not including the one pointed to by . Range is valid if and only if both refer to the same data structure. The result of the application of the algorithms in the library to invalid ranges is undefined.
Warning: Please note that the definition of a range is different to that of iterators. An interface of a data structure must declare whether it works with iterators, circulators, or both. STL algorithms always specify only iterators in their interfaces. A range of circulators used in an interface for iterators will work as expected as long as . A range will be interpreted as the empty range like for iterators, which is different than the full range that it should denote for circulators.
Algorithms could be written to support both, iterators and circulators, in a single interface. Here, the range would be interpreted correctly. For more information how to program functions with this behavior, please refer to the CGAL Reference Manual.
As we said in the introduction, we are a little bit sloppy in the presentation, in order to make it easier to understand. A class is said to be a circulator if it fulfills a set of requirements. In the following sections we do not present the requirements, but we state properties that are true, if the requirements are fulfilled. The difference is best seen by an example: We write that the return value of the test for equality returns a bool. The requirement is only that the return value is convertible to bool.
Requirements for Circulators |
Container_from_circulator<C> |
Circulator_from_iterator<I> |
Circulator_from_container<C> |