diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index 1a6880963..6744ed23c 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -22,14 +22,46 @@ /** @file iter-adapter.hpp ** Helper template(s) for creating lumiera forward iterators. - ** This denotes a concept similar to STL's "forward iterator", with - ** the addition of an bool check to detect iteration end. The latter - ** is inspired by the \c hasNext() function found in many current - ** languages supporting iterators. In a similar vein (inspired from - ** functional programming), we deliberately don't support the various - ** extended iterator concepts from STL and boost (random access iterators, - ** output iterators and the like). According to this concept, an iterator - ** is a promise for pulling values -- and nothing beyond that. + ** Usually, these templates will be created and provided by a custom + ** container type and accessed by the client through a typedef name + ** "iterator" (similar to the usage within the STL). For more advanced + ** usage, the providing container might want to subclass these iterators, + ** e.g. to provide an additional, specialised API. + ** + ** Depending on the concrete situation, there are several flavours + ** - the IterAdapter retains an active callback connection to the + ** controlling container, thus allowing arbitrary complex behaviour. + ** - the RangeIter allows just to expose a range of elements defined + ** by a STL-like pair of "start" and "end" iterators + ** - often, objects are managed internally by pointers, while allowing + ** the clients to use direct references; to support this usage scenario, + ** PtrDerefIter wraps an existing iterator, while dereferencing any value + ** automatically on access. + ** + ** + ** \par Lumiera forward iterator concept + ** + ** Similar to the STL, instead of using a common "Iterator" base class, + ** instead we define a common set of functions and behaviour which can + ** be expected from any such iterator. These rules are similar to STL's + ** "forward iterator", with the addition of an bool check to detect + ** iteration end. The latter s inspired by the \c hasNext() function + ** found in many current languages supporting iterators. In a similar + ** vein (inspired from functional programming), we deliberately don't + ** support the various extended iterator concepts from STL and boost + ** (random access iterators, output iterators and the like). According + ** to this concept, an iterator is a promise for pulling values, + ** and nothing beyond that. + ** + ** - Any Lumiera forward iterator can be in a "exhausted" (invalid) state, + ** which can be checked by the bool conversion. Especially, any instance + ** created by the default ctor is always fixed to that state. This + ** state is final and can't be reset, meaning that any iterator is + ** a disposable one-way-off object. + ** - iterators are copyable and comparable + ** - when an iterator is \em not in the exhausted state, it may be + ** \em dereferenced to yield the "current" value. + ** - moreover, iterators may be incremented until exhaustion. ** ** @todo WIP WIP WIP ** @todo see Ticket #182 @@ -45,20 +77,17 @@ #include "lib/error.hpp" #include "lib/bool-checkable.hpp" -#include - namespace lib { - using boost::remove_pointer; - /** * Adapter for building an implementation of the lumiera forward iterator concept. * The "current position" is represented as an opaque element (usually an nested iterator), - * with callbacks to the controlling container instance for managing this position. + * with callbacks to the controlling container instance for managing this position. + * This allows to influence and customise the iteration process to a large extent. * Basically such an IterAdapter behaves like the similar concept from STL, but * - it is not just a disguised pointer (meaning, it's more expensive) * - it checks validity on every operation and may throw @@ -86,7 +115,7 @@ namespace lib { : source_(src) , pos_(startpos) { - CON::iterValid(source_,pos_); + checkPos(); } IterAdapter () @@ -115,7 +144,7 @@ namespace lib { operator++() { _maybe_throw(); - CON::iterNext (source_,pos_); + iterate(); return *this; } @@ -124,14 +153,14 @@ namespace lib { { _maybe_throw(); IterAdapter oldPos(*this); - CON::iterNext (source_,pos_); + iterate(); return oldPos; } bool isValid () const { - return (source_ && CON::iterValid(source_,pos_)); + return checkPos(); } bool @@ -141,6 +170,34 @@ namespace lib { } + protected: /* === iteration control interface === */ + + /** ask the controlling container if this position is valid. + * @note this function is called before any operation, + * thus the container may adjust the position value, + * for example setting it to a "stop iteration" mark. + */ + bool + checkPos() const + { + return source_ && CON::hasNext (source_,pos_); + } + + /** ask the controlling container to yield the next position. + * The call is dispatched only if the current position is valid; + * any new position returned is again validated, so to detect + * the iteration end as soon as possible. + */ + bool + iterate () + { + if (!checkPos()) return false; + + CON::iterNext (source_,pos_); + return checkPos(); + } + + private: void @@ -169,11 +226,136 @@ namespace lib { - /** wrapper for an existing Iterator type, - * automatically dereferencing the output of the former. - * For this to work, the "source" iterator is expected - * to be declared on \em pointers rather than on values. - * @note bool checkable if and only if source is... + /** + * Accessing a STL element range through a Lumiera forward iterator, + * An instance of this iterator adapter is completely self-contained + * and allows to iterate once over the range of elements, until + * \c pos==end . Thus, a custom container may expose a range of + * elements of an embedded STL container, without controlling + * the details of the iteration (as is possible using the + * more generic IterAdapter). + */ + template + class RangeIter + : public lib::BoolCheckable > + { + IT p_; + IT e_; + + public: + typedef typename IT::pointer pointer; + typedef typename IT::reference reference; + typedef typename IT::value_type value_type; + + RangeIter (IT const& start, IT const& end) + : p_(start) + , e_(end) + { } + + RangeIter () + : p_(0) + , e_(0) + { } + + + /* === lumiera forward iterator concept === */ + + reference + operator*() const + { + _maybe_throw(); + return *p_; + } + + pointer + operator->() const + { + _maybe_throw(); + return &(*p_); + } + + RangeIter& + operator++() + { + ++p_; + return *this; + } + + RangeIter + operator++(int) + { + return RangeIter (p_++,e_); + } + + bool + isValid () const + { + return (p_!= IT(0)) && (p_ != e_); + } + + bool + empty () const + { + return !isValid(); + } + + private: + + void + _maybe_throw() const + { + if (!isValid()) + throw lumiera::error::Invalid ("Can't iterate further", + lumiera::error::LUMIERA_ERROR_ITER_EXHAUST); + } + + + /// comparison operator is allowed to access the underlying impl iterator + template + friend bool operator== (RangeIter const&, RangeIter const&); + }; + + + + /// Supporting equality comparisons... + template + bool operator== (RangeIter const& il, RangeIter const& ir) { return (!il && !ir) || (il.p_ == ir.p_); } + + template + bool operator!= (RangeIter const& il, RangeIter const& ir) { return !(il == ir); } + + + + + + namespace { + + /** helper to remove pointer, + * while retaining const */ + template + struct RemovePtr { typedef T Type; }; + + template + struct RemovePtr { typedef T Type; }; + + template + struct RemovePtr { typedef const T Type; }; + + template + struct RemovePtr { typedef const T Type; }; + + template + struct RemovePtr { typedef const T Type; }; + + } + + + /** + * wrapper for an existing Iterator type, + * automatically dereferencing the output of the former. + * For this to work, the "source" iterator is expected + * to be declared on \em pointers rather than on values. + * @note bool checkable if and only if source is... */ template class PtrDerefIter @@ -183,7 +365,7 @@ namespace lib { public: typedef typename IT::value_type pointer; - typedef typename remove_pointer::type value_type; + typedef typename RemovePtr::Type value_type; typedef value_type& reference; diff --git a/tests/lib/iter-adapter-test.cpp b/tests/lib/iter-adapter-test.cpp index d7935dd33..d49878f62 100644 --- a/tests/lib/iter-adapter-test.cpp +++ b/tests/lib/iter-adapter-test.cpp @@ -49,7 +49,14 @@ namespace test{ uint NUM_ELMS = 10; - + /** + * Example of a more elaborate custom container exposing an iteration API. + * While the demo implementation here is based on pointers within a vector, + * we hand out a IterAdapter, which will call back when used by the client, + * thus allowing us to control the iteration process. Moreover, we provide + * a variant of this iterator, which automatically dereferences the pointers, + * thus yielding direct references for the client code to use. + */ class TestContainer { typedef vector _Vec; @@ -58,6 +65,7 @@ namespace test{ static void killIt (int *it) { delete it; } + public: TestContainer (uint count) : numberz_(count) @@ -70,19 +78,21 @@ namespace test{ { for_each (numberz_, killIt); } - + + + /* ==== Exposing Iterator interface(s) for the clients ====== */ typedef IterAdapter<_Vec::iterator, TestContainer> iterator; typedef IterAdapter<_Vec::const_iterator, TestContainer> const_iterator; typedef PtrDerefIter ref_iterator; typedef PtrDerefIter const_ref_iter; - - + + iterator begin () { return iterator (this, numberz_.begin()); } const_iterator begin () const { return const_iterator (this, numberz_.begin()); } ref_iterator begin_ref () { return ref_iterator (begin()); } const_ref_iter begin_ref () const { return const_ref_iter (begin()); } - + iterator end () { return iterator(); } const_iterator end () const { return const_iterator(); } @@ -93,16 +103,12 @@ namespace test{ friend class IterAdapter<_Vec::const_iterator,TestContainer>; - /** Implementation of Iteration-logic: pull next element. - * Implicitly this includes a test for iteration end. - */ + /** Implementation of Iteration-logic: pull next element. */ template static void - iterNext (const TestContainer* src, ITER& pos) + iterNext (const TestContainer*, ITER& pos) { - if (iterValid(src,pos)) - ++pos; - iterValid(src,pos); + ++pos; } /** Implementation of Iteration-logic: detect iteration end. @@ -115,7 +121,7 @@ namespace test{ */ template static bool - iterValid (const TestContainer* src, ITER& pos) + hasNext (const TestContainer* src, ITER& pos) { REQUIRE (src); if ((pos != ITER(0)) && (pos != src->numberz_.end())) @@ -124,17 +130,22 @@ namespace test{ { pos = ITER(0); return false; - } - } + } } }; - } + + } // (END) impl test dummy container - /******************************************************************** - * @test create an iterator element for a given container and - * verify its behaviour in accordance to the concept - * "lumiera forward iterator" + + + + + /********************************************************************* + * @test set up example implementations based on the iterator-adapter + * templates and verify the behaviour in accordance to the + * concept "lumiera forward iterator" + * * @todo see Ticket #182 */ class IterAdapter_test : public Test @@ -145,15 +156,44 @@ namespace test{ { if (0 < arg.size()) NUM_ELMS = lexical_cast (arg[0]); + wrapIterRange (); + TestContainer testElms (NUM_ELMS); simpleUsage (testElms); iterTypeVariations (testElms); + verifyComparisons (testElms); } - static void showIt (int* elm) { cout << "::" << *elm; } + /** @test usage scenario, where we allow the client to + * access a range of elements given by STL iterators, + * without any specific iteration behaviour. + */ + void + wrapIterRange () + { + vector iVec (NUM_ELMS); + for (uint i=0; i < NUM_ELMS; ++i) + iVec[i] = i; + + typedef vector::iterator I; + typedef RangeIter Range; + + Range range (iVec.begin(), iVec.end()); + ASSERT (!isnil (range) || !NUM_ELMS); + + // now for example the client could.... + while ( range ) + cout << "::" << *range++; + + cout << endl; + ASSERT (isnil (range)); + ASSERT (range == Range()); + } + + /** @test use the IterAdapter as if it was a STL iterator */ void simpleUsage (TestContainer& elms) { @@ -161,7 +201,12 @@ namespace test{ cout << endl; } + static void showIt (int* elm) { cout << "::" << *elm; } + + + /** @test verify the const and dereferencing variants, + * which can be created based on IterAdapter */ void iterTypeVariations (TestContainer& elms) { @@ -191,7 +236,7 @@ namespace test{ // note: the previous run indeed modified // the element within the container. - // --(**iter); // doesn't compile, because it's const ///////////////////////////////////TODO: duh! it *does* compile. why? + // ++(*iter); // doesn't compile, because it yields a "* const" } i = 0; @@ -212,8 +257,17 @@ namespace test{ { ASSERT (iter); ASSERT ((*iter) == i); + + // *iter = i+1; ///////////TODO this should be const, but it isn't } - + } + + + + /** @test iterator comparison, predicates and operators */ + void + verifyComparisons (TestContainer& elms) + { TestContainer::ref_iterator rI (elms.begin_ref()); ASSERT (0 == *rI ); @@ -221,6 +275,19 @@ namespace test{ ASSERT (1 == *rI ); ASSERT (2 == *++rI); + TestContainer const& const_elms (elms); + TestContainer::const_ref_iter rI2 (const_elms.begin_ref()); + + ASSERT (rI2 != rI); + ASSERT (rI2 == elms.begin_ref()); + ASSERT (rI2 == const_elms.begin_ref()); + + ++++rI2; + + ASSERT (rI2 == rI); + ASSERT (rI2 != ++rI); + ASSERT (!isnil (rI2)); + ASSERT (TestContainer::iterator() == elms.end()); ASSERT (!(TestContainer::iterator())); ASSERT (!(elms.end()));