reworked IterAdapter, added RangeIter for STL ranges. Should do for now

This commit is contained in:
Fischlurch 2009-07-15 07:26:49 +02:00
parent b2f72ef0fc
commit a0187847da
2 changed files with 295 additions and 46 deletions

View file

@ -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;

View file

@ -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()));