From ce7c38312dae72b2ff6531f17c215f5055e6d6a6 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 5 Jun 2015 18:32:42 +0200 Subject: [PATCH] iterator improvements: use Lumiera Forward Iterator in range for loops This is kind of the logic consequence, since we consider our functional iterator concept still superior and will continue to rely on it. For some time now, I've considered to build a generic bridge function, to use enable_if and metaprogramming to figure out if some type is a "Lumiera Forward Iterator" automatically. But since our concept is to some degree a contract regarding semantics, which never can be captured by any kind of introspection, such a bridge implementation would be rather heuristic and bears the danger to trigger on types actually not intended as iterator at all. So I consider such a solution as dangerous and we'll settle with just supplying the necessary bridge functions as free functions injected for ADL on a case by case base --- src/lib/iter-adapter-ptr-deref.hpp | 878 +++++++++++++++++++++++++++++ src/lib/iter-adapter.hpp | 19 +- src/lib/itertools.hpp | 9 +- 3 files changed, 902 insertions(+), 4 deletions(-) create mode 100644 src/lib/iter-adapter-ptr-deref.hpp diff --git a/src/lib/iter-adapter-ptr-deref.hpp b/src/lib/iter-adapter-ptr-deref.hpp new file mode 100644 index 000000000..ef3332ddd --- /dev/null +++ b/src/lib/iter-adapter-ptr-deref.hpp @@ -0,0 +1,878 @@ +/* + ITER-ADAPTER-PTR-DEREF.hpp - helpers for building simple forward iterators + + Copyright (C) Lumiera.org + 2009, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file iter-adapter.hpp + ** Helper template(s) for creating lumiera forward iterators. + ** These are the foundation to build up iterator like types from scratch. + ** 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, several flavours are provided: + ** - the IterAdapter retains an active callback connection to the + ** controlling container, thus allowing arbitrary complex behaviour. + ** - the IterStateWrapper uses a variation of that approach, where the + ** representation of the current state is embedded as an state value + ** element right into the iterator instance. + ** - 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. + ** - for some (very specific) usage situations we intend to explore the + ** contents of a stable and unmodifiable data structure through pointers. + ** The AddressExposingIter wraps another Lumiera Forward Iterator and + ** exposes addresses -- assuming the used source iterator is exposing + ** references to pre-existing storage locations (not temporaries). + ** + ** There are many further ways of yielding a Lumiera forward iterator. + ** For example, lib::IterSource builds a "iterable" source of data elements, + ** while hiding the actual container or generator implementation behind a + ** vtable call. Besides, there are adapters for the most common usages + ** with STL containers, and such iterators can also be combined and + ** extended with the help of itertools.hpp + ** + ** Basically every class in compliance with our specific iterator concept + ** can be used as a building block within this framework. + ** + ** + ** \par Lumiera forward iterator concept + ** + ** Similar to the STL, instead of using a common "Iterator" base class, + ** we rather 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 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, arithmetics, difference + ** between 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 equality 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. + ** + ** @see iter-adapter-test.cpp + ** @see itertools.hpp + ** @see IterSource (completely opaque iterator) + ** @see iter-type-binding.hpp + ** + */ + + +#ifndef LIB_ITER-ADAPTER-PTR-DEREF_H +#define LIB_ITER-ADAPTER-PTR-DEREF_H + + +#include "lib/error.hpp" +#include "lib/bool-checkable.hpp" +#include "lib/iter-type-binding.hpp" + +#include + + +namespace lib { + + + namespace { // internal helpers + void + _throwIterExhausted() + { + throw lumiera::error::Invalid ("Can't iterate further", + lumiera::error::LUMIERA_ERROR_ITER_EXHAUST); + } + } + + + + /** + * Adapter for building an implementation of the lumiera forward iterator concept. + * The "current position" is represented as an opaque element (usually a nested iterator), + * with callbacks into the controlling container instance to manage 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 + * - it has a distinct back-link to the source container + * - the source container needs to provide hasNext() and iterNext() free functions. + * - we may need friendship to implement those extension points on the container + * - the end-of-iteration can be detected by bool check + * @note it is possible to "hide" a smart-ptr within the CON template parameter. + * + * \par Stipulations + * - POS refers to the current position within the data source of this iterator. + * -# it should be default constructible + * -# it should be copy constructible + * -# when IterAdapter is supposed to be assignable, then POS should be + * -# it should provide embedded typedefs for pointer, reference and value_type, + * or alternatively resolve these types through specialisation of iter::TypeBinding. + * -# it should be convertible to the pointer type it declares + * -# dereferencing should yield a type that is convertible to the reference type + * - CON points to the data source of this iterator (typically a data container type) + * We store a pointer-like backlink to invoke a special iteration control API: + * -# \c checkPoint yields true iff the source has yet more result values to yield + * -# \c iterNext advances the POS to the next element + * + * @see scoped-ptrvect.hpp usage example + * @see iter-type-binding.hpp + * @see iter-adaptor-test.cpp + */ + template + class IterAdapter + : public lib::BoolCheckable > + { + CON source_; + mutable POS pos_; + + public: + typedef typename iter::TypeBinding::pointer pointer; + typedef typename iter::TypeBinding::reference reference; + typedef typename iter::TypeBinding::value_type value_type; + + IterAdapter (CON src, POS const& startpos) + : source_(src) + , pos_(startpos) + { + check(); + } + + IterAdapter () + : source_() + , pos_() + { } + + + /* === lumiera forward iterator concept === */ + + reference + operator*() const + { + _maybe_throw(); + return *pos_; + } + + pointer + operator->() const + { + _maybe_throw(); + return pos_; + } + + IterAdapter& + operator++() + { + _maybe_throw(); + iterate(); + return *this; + } + + bool + isValid () const + { + return check(); + } + + bool + empty () const + { + return !isValid(); + } + + + 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 + check() const + { + return source_ && checkPoint (source_,pos_); // extension point: free function checkPoint(...) + } + + /** ask the controlling container to yield the next position. + * The call is dispatched only if the current position is valid; + * any new position reached will typically be validated prior + * to any further access, through invocation of #check. + */ + void + iterate() + { + if (check()) + iterNext (source_,pos_); // extension point: free function iterNext(...) + } + + + private: + void + _maybe_throw() const + { + if (!isValid()) + _throwIterExhausted(); + } + + /// comparison is allowed to access impl iterator + template + friend bool operator== (IterAdapter const&, IterAdapter const&); + }; + + + /// Supporting equality comparisons... + template + bool operator== (IterAdapter const& il, IterAdapter const& ir) { return il.pos_ == ir.pos_; } + + template + bool operator!= (IterAdapter const& il, IterAdapter const& ir) { return !(il == ir); } + + + + + /** + * Another Lumiera Forward Iterator building block, based on incorporating a state type + * right into the iterator. Contrast this to IterAdapter, which refers to a managing + * container behind the scenes. Here, all of the state is assumed to live in the + * custom type embedded into this iterator, accessed and manipulated through + * a set of free functions, picked up through ADL. + * + * \par Assumptions when building iterators based on IterStateWrapper + * There is a custom state representation type ST. + * - default constructible + * - this default state represents the \em bottom (invalid) state. + * - copyable, because iterators are passed by value + * - this type needs to provide an iteration control API through free functions + * -# \c checkPoint establishes if the given state element represents a valid state + * -# \c iterNext evolves this state by one step (sideeffect) + * -# \c yield realises the given state, yielding an element of result type T + * + * @see IterExplorer an iterator monad built on top of IterStateWrapper + * @see iter-explorer-test.hpp + * @see iter-adaptor-test.cpp + */ + template + class IterStateWrapper + : public lib::BoolCheckable > + { + ST core_; + + public: + typedef T* pointer; + typedef T& reference; + typedef T value_type; + + IterStateWrapper (ST const& initialState) + : core_(initialState) + { + checkPoint (core_); // extension point: checkPoint + } + + IterStateWrapper () + : core_() + { } + + + /* === lumiera forward iterator concept === */ + + reference + operator*() const + { + __throw_if_empty(); + return yield (core_); // extension point: yield + } + + pointer + operator->() const + { + __throw_if_empty(); + return & yield(core_); // extension point: yield + } + + IterStateWrapper& + operator++() + { + __throw_if_empty(); + iterNext (core_); // extension point: iterNext + return *this; + } + + bool + isValid () const + { + return checkPoint(core_); // extension point: checkPoint + } + + bool + empty () const + { + return !isValid(); + } + + protected: + + /** allow derived classes to + * access state representation */ + ST & + stateCore() + { + return core_; + } + + void + __throw_if_empty() const + { + if (!isValid()) + _throwIterExhausted(); + } + + + /// comparison is allowed to access state implementation core + template + friend bool operator== (IterStateWrapper const&, IterStateWrapper const&); + }; + + + + /// Supporting equality comparisons of equivalent iterators (same state type)... + template + bool operator== (IterStateWrapper const& il, IterStateWrapper const& ir) + { + return (il.empty() && ir.empty()) + || (il.isValid() && ir.isValid() && il.core_ == ir.core_); + } + + template + bool operator!= (IterStateWrapper const& il, IterStateWrapper const& ir) + { + return ! (il == ir); + } + + + + + + + + + /** + * 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 iter::TypeBinding::pointer pointer; + typedef typename iter::TypeBinding::reference reference; + typedef typename iter::TypeBinding::value_type value_type; + + RangeIter (IT const& start, IT const& end) + : p_(start) + , e_(end) + { } + + RangeIter () + : p_() + , e_() + { } + + + /** allow copy, + * when the underlying iterators + * are compatible or convertible */ + template + RangeIter (I2 const& oIter) + : p_(oIter.getPos()) + , e_(oIter.getEnd()) + { } + + + /* === lumiera forward iterator concept === */ + + reference + operator*() const + { + _maybe_throw(); + return *p_; + } + + pointer + operator->() const + { + _maybe_throw(); + return &(*p_); + } + + RangeIter& + operator++() + { + _maybe_throw(); + ++p_; + return *this; + } + + bool + isValid () const + { + return (p_!= IT()) && (p_ != e_); + } + + bool + empty () const + { + return !isValid(); + } + + + /** access wrapped STL iterator */ + const IT& getPos() const { return p_; } + const IT& getEnd() const { return e_; } + + + private: + + void + _maybe_throw() const + { + if (!isValid()) + _throwIterExhausted(); + } + }; + + + + /// Supporting equality comparisons... + template + bool operator== (RangeIter const& il, RangeIter const& ir) { return (!il && !ir) || (il.getPos() == ir.getPos()); } + + 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; }; + + } + + + /** + * Helper for type rewritings: + * get the element type for an iterator like entity + */ + template + struct IterType; + + template class Iter, class TY, class CON> + struct IterType > + { + typedef CON Container; + typedef TY ElemType; + + template + struct SimilarIter ///< rebind to a similarly structured Iterator with value type T2 + { + typedef Iter Type; + }; + }; + + template + struct IterType > + : IterType + { + template + struct SimilarIter ///< rebind to rewritten Iterator wrapped into RangeIter + { + typedef typename IterType::template SimilarIter::Type WrappedIter; + typedef RangeIter 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 + : public lib::BoolCheckable > + { + IT i_; ///< nested source iterator + + + public: + typedef typename IT::value_type pointer; + typedef typename RemovePtr::Type value_type; + typedef value_type& reference; + + // for use with STL algorithms + typedef void difference_type; + typedef std::forward_iterator_tag iterator_category; + + + // the purpose of the following typedefs is to ease building a correct "const iterator" + + typedef typename boost::remove_const::type ValueTypeBase; // value_type without const + + typedef typename IterType::template SimilarIter< ValueTypeBase* * >::Type WrappedIterType; + typedef typename IterType::template SimilarIter::Type WrappedConstIterType; + + typedef PtrDerefIter IterType; + typedef PtrDerefIter ConstIterType; + + + + /** PtrDerefIter is always created + * by wrapping an existing iterator. + */ + explicit + PtrDerefIter (IT srcIter) + : i_(srcIter) + { } + + + /** allow copy initialisation also when + * the wrapped iterator is based on some variation of a pointer. + * Especially, this includes initialisation of the "const variant" + * from the "normal variant" of PtrDerefIter. Actually, we need to convert + * in this case by brute force, because indeed (const TY *)* is not assignable + * from (TY *)* -- just we know that our intention is to dereference both levels + * of pointers, and then the resulting conversion is correct. + * @note in case IT == WrappedIterType, this is just a redefinition of the + * default copy ctor. In all other cases, this is an additional + * ctor besides the default copy ctor */ + PtrDerefIter (PtrDerefIter const& oIter) + : i_(reinterpret_cast (oIter.getBase())) + { } + + PtrDerefIter& + operator= (PtrDerefIter const& ref) + { + i_ = reinterpret_cast (ref.getBase()); + return *this; + } + + + /** explicit builder to allow creating a const variant from the basic srcIter type. + * Again, the reason necessitating this "backdoor" is that we want to swallow one level + * of indirection. Generally speaking `const T **` is not the same as `T * const *`, + * but in our specific case the API ensures that a `PtrDerefIter` + * only exposes const elements. + */ + static PtrDerefIter + build_by_cast (WrappedIterType const& srcIter) + { + return PtrDerefIter (reinterpret_cast (srcIter)); + } + + static PtrDerefIter + nil() + { + return PtrDerefIter (IT()); + } + + + + + + + /* === lumiera forward iterator concept === */ + + reference + operator*() const + { + return *(*i_); + } + + pointer + operator->() const + { + return *i_; + } + + PtrDerefIter& + operator++() + { + ++i_; + return *this; + } + + bool + isValid () const + { + return bool(i_); + } + + bool + empty () const + { + return !isValid(); + } + + + /** access the wrapped implementation iterator */ + IT const& + getBase() const + { + return i_; + } + }; + + + /// Supporting equality comparisons... + template + bool operator== (PtrDerefIter const& il, PtrDerefIter const& ir) { return il.getBase() == ir.getBase(); } + + template + bool operator!= (PtrDerefIter const& il, PtrDerefIter const& ir) { return !(il == ir); } + + + + + + /** + * wrapper for an existing Iterator type to expose the address of each value yielded. + * Typically this can be used to build visitation sequences based on values living + * within a stable data structure (e.g. unmodifiable STL vector) + * @warning use of this wrapper might lead to taking the address of temporaries. + * The continued existence of the exposed storage locations must be guaranteed. + * @note bool checkable if and only if source is... + */ + template + class AddressExposingIter + : public lib::BoolCheckable > + { + typedef typename IT::pointer _Ptr; + + IT i_; ///< nested source iterator + + mutable _Ptr currPtr_; + + + void + takeAddress() + { + if (i_.isValid()) + currPtr_ = & (*i_); + else + currPtr_ = 0; + } + + + public: + typedef typename IT::pointer const* pointer; + typedef typename IT::pointer const& reference; + typedef typename IT::pointer const value_type; + + + /** AddressExposingIter is always created + * by wrapping an existing iterator. + */ + explicit + AddressExposingIter (IT srcIter) + : i_(srcIter) + { + takeAddress(); + } + + + + + + /* === lumiera forward iterator concept === */ + + /** @return address of the source iteraor's current result + * @warning exposing a reference to an internal pointer for sake of compatibility. + * Clients must not store that reference, but rather use it to initialise + * a copy. The internal pointer exposed here will be changed on increment. + */ + reference + operator*() const + { + return currPtr_; + } + + _Ptr + operator->() const + { + return currPtr_; + } + + AddressExposingIter& + operator++() + { + ++i_; + takeAddress(); + return *this; + } + + bool + isValid () const + { + return bool(i_); + } + + bool + empty () const + { + return !isValid(); + } + + + /** access the wrapped implementation iterator */ + IT const& + getBase() const + { + return i_; + } + }; + + + /// Supporting equality comparisons... + template + bool operator== (AddressExposingIter const& il, AddressExposingIter const& ir) { return il.getBase() == ir.getBase(); } + + template + bool operator!= (AddressExposingIter const& il, AddressExposingIter const& ir) { return !(il == ir); } + + + + + + + /** wrapper to declare exposed values const */ + template + class ConstIter + : public lib::BoolCheckable > + { + IT i_; ///< nested source iterator + + + public: + typedef const typename IT::value_type value_type; + typedef const typename IT::pointer pointer; + typedef const typename IT::reference reference; + + ConstIter (IT srcIter) + : i_(srcIter) + { } + + + + + /* === lumiera forward iterator concept === */ + + reference + operator*() const + { + return *i_; + } + + pointer + operator->() const + { + return i_.operator->(); + } + + ConstIter& + operator++() + { + ++i_; + return *this; + } + + bool + isValid () const + { + return bool(i_); + } + + bool + empty () const + { + return !isValid(); + } + + + /** access the wrapped implementation iterator */ + IT const& + getBase() const + { + return i_; + } + }; + + + /// Supporting equality comparisons... + template + bool operator== (ConstIter const& il, ConstIter const& ir) { return il.getBase() == ir.getBase(); } + + template + bool operator!= (ConstIter const& il, ConstIter const& ir) { return !(il == ir); } + + + +}// namespace lib +#endif /*LIB_ITER-ADAPTER-PTR-DEREF_H*/ diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index 1f668f446..2c05650d0 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -114,6 +114,12 @@ namespace lib { } } + /** use a given Lumiera Forward Iterator in standard "range for loops" */ +#define ENABLE_USE_IN_STD_RANGE_FOR_LOOPS(ITER) \ + friend ITER begin (ITER const& it){ return it; } \ + friend ITER&& begin (ITER&& it) { return static_cast (it); } \ + friend ITER end (ITER const&) { return ITER(); } + /** @@ -244,6 +250,9 @@ namespace lib { _throwIterExhausted(); } + + ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (IterAdapter); + /// comparison is allowed to access impl iterator template friend bool operator== (IterAdapter const&, IterAdapter const&); @@ -357,6 +366,9 @@ namespace lib { } + + ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (IterStateWrapper); + /// comparison is allowed to access state implementation core template friend bool operator== (IterStateWrapper const&, IterStateWrapper const&); @@ -469,6 +481,9 @@ namespace lib { const IT& getEnd() const { return e_; } + ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (RangeIter); + + private: void @@ -874,5 +889,5 @@ namespace lib { -} // namespace lib -#endif +}// namespace lib +#endif /*LIB_ITER_ADAPTER_H*/ diff --git a/src/lib/itertools.hpp b/src/lib/itertools.hpp index 69ff3124a..f33d972fd 100644 --- a/src/lib/itertools.hpp +++ b/src/lib/itertools.hpp @@ -281,8 +281,8 @@ namespace lib { } - mutable bool isOK_; mutable bool cached_; + mutable bool isOK_; bool currVal_isOK () const ///< @return (maybe cached) result of filter predicate @@ -304,7 +304,8 @@ namespace lib { FilterCore (IT const& source, PRED prediDef) : Raw(source) , predicate_(prediDef) // induces a signature check - , cached_(false) + , cached_(false) // not yet cached + , isOK_() // some value { } }; @@ -331,6 +332,8 @@ namespace lib { FilterIter (IT const& src, PRED filterPredicate) : _Impl(_Filter(src,filterPredicate)) { } + + ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (FilterIter) }; @@ -471,6 +474,8 @@ namespace lib { TransformIter (IT const& src, FUN trafoFunc) : _IteratorImpl(_Trafo(src,trafoFunc)) { } + + ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (TransformIter) };