reworked IterAdapter, added RangeIter for STL ranges. Should do for now
This commit is contained in:
parent
b2f72ef0fc
commit
a0187847da
2 changed files with 295 additions and 46 deletions
|
|
@ -22,14 +22,46 @@
|
|||
|
||||
/** @file iter-adapter.hpp
|
||||
** Helper template(s) for creating <b>lumiera forward iterators</b>.
|
||||
** 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, <i>an iterator
|
||||
** is a promise for pulling values</i> -- 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, <i>an iterator is a promise for pulling values,</i>
|
||||
** 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 <boost/type_traits/remove_pointer.hpp>
|
||||
|
||||
|
||||
|
||||
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 IT>
|
||||
class RangeIter
|
||||
: public lib::BoolCheckable<RangeIter<IT> >
|
||||
{
|
||||
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<class I1, class I2>
|
||||
friend bool operator== (RangeIter<I1> const&, RangeIter<I2> const&);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/// Supporting equality comparisons...
|
||||
template<class I1, class I2>
|
||||
bool operator== (RangeIter<I1> const& il, RangeIter<I2> const& ir) { return (!il && !ir) || (il.p_ == ir.p_); }
|
||||
|
||||
template<class I1, class I2>
|
||||
bool operator!= (RangeIter<I1> const& il, RangeIter<I2> const& ir) { return !(il == ir); }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/** helper to remove pointer,
|
||||
* while retaining const */
|
||||
template<typename T>
|
||||
struct RemovePtr { typedef T Type; };
|
||||
|
||||
template<typename T>
|
||||
struct RemovePtr<T*> { typedef T Type; };
|
||||
|
||||
template<typename T>
|
||||
struct RemovePtr<const T*> { typedef const T Type; };
|
||||
|
||||
template<typename T>
|
||||
struct RemovePtr<T* const> { typedef const T Type; };
|
||||
|
||||
template<typename T>
|
||||
struct RemovePtr<const T* const> { 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 IT>
|
||||
class PtrDerefIter
|
||||
|
|
@ -183,7 +365,7 @@ namespace lib {
|
|||
|
||||
public:
|
||||
typedef typename IT::value_type pointer;
|
||||
typedef typename remove_pointer<pointer>::type value_type;
|
||||
typedef typename RemovePtr<pointer>::Type value_type;
|
||||
typedef value_type& reference;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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<int *> _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<iterator > ref_iterator;
|
||||
typedef PtrDerefIter<const_iterator> 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<class ITER>
|
||||
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<class ITER>
|
||||
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<uint> (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<int> iVec (NUM_ELMS);
|
||||
for (uint i=0; i < NUM_ELMS; ++i)
|
||||
iVec[i] = i;
|
||||
|
||||
typedef vector<int>::iterator I;
|
||||
typedef RangeIter<I> 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()));
|
||||
|
|
|
|||
Loading…
Reference in a new issue