lumiera_documentation/src/lib/iter-adapter.hpp

1222 lines
39 KiB
C++
Raw Normal View History

2009-07-11 19:23:20 +02:00
/*
ITER-ADAPTER.hpp - helpers for building simple forward iterators
2010-12-17 23:28:49 +01:00
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
Copyright (C)
2009, Hermann Vosseler <Ichthyostega@web.de>
2017,2024, Hermann Vosseler <Ichthyostega@web.de>
2010-12-17 23:28:49 +01:00
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
  **Lumiera** 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. See the file COPYING for further details.
2010-12-17 23:28:49 +01:00
2009-07-11 19:23:20 +02:00
*/
/** @file iter-adapter.hpp
** Helper template(s) for creating *Lumiera Forward Iterators*.
2011-11-19 18:01:48 +01:00
** 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.
**
2011-11-19 18:01:48 +01:00
** 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 a *State Core*
** value element right into the iterator instance.
** - very similar is IterableDecorator, but this time directly as
** decorator to inherit from the »state core«, and without checks.
** - the RangeIter allows just to expose a range of elements defined
** by a STL-like pair of "start" and "end" iterators; thus it
** serves a bridge between STL iterators and Lumiera iterators.
**
** Some more specific use cases are provided in the extension header
** iter-adapter-ptr-deref.hpp
** - 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
2011-11-19 18:01:48 +01:00
** 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).
2011-11-19 18:01:48 +01:00
**
** There are many further ways of building a Lumiera Forward Iterator.
** For example, lib::IterSource exposes an "iterable" source of data elements,
2011-11-19 18:01:48 +01:00
** while hiding the actual container or generator implementation behind a
** VTable call. Furthermore, complex processing chains with recursive
** expansion can be built with the \ref IterExporer builder function.
** 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 \ref itertools.hpp
2011-11-19 18:01:48 +01:00
**
** Basically every class in compliance with our specific iterator concept
** can be used as a building block within this framework.
**
**
** # 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
** "input iterator", with the addition of an bool check to detect
** iteration end. The latter is inspired by the `hasNext()` function
** found in many current languages supporting iterators. However, by
** drawing from insights promoted in functional programming, deliberately
** we do not support the various extended iterator concepts from the STL
2011-11-19 18:01:48 +01:00
** (random access iterators, output iterators, arithmetics, difference
** between iterators and the like). According to this concept proposed
** here, _an iterator is a promise for pulling values once,_
** and nothing beyond that.
**
** Notably,
** - any Lumiera forward iterator can be in a "exhausted" (invalid) state,
** which can be checked by the bool conversion. Especially, an instance
** created by the default ctor is always fixed to that state. This
** state is final and can not be reset, meaning that any iterator
** is a disposable one-way-off object.
2011-11-19 18:01:48 +01:00
** - iterators are copyable and equality comparable
** - when an iterator is _not_ in the exhausted state, it may be
** _dereferenced_ to yield the _current value_.
** - usually, the _current value_ is exposed by-ref (but by-val is possible)
** - moreover, iterators may be incremented until exhaustion.
2009-07-11 19:23:20 +02:00
**
** Conceptually, a Lumiera Iterator represents a lazy stream of calculations
** rather than a target value considered to be »within« a container. And while
** the result is in may cases deliberately _exposed as a reference,_ in order
** to allow for tightly coupled implementation in special cases -- for the
** typical usage it is _discouraged_ to assume anything about the source,
** beyond the limited access to some transient state as exposed during
** active iteration. Together, these rules enable a _loose coupling_
** to the source of data.
**
** ## Usage
**
** All the building blocks defined her provide the bridge definitions to allow
** using a »Lumiera Forward Iterator« in common language constructs: It can be
** used directly in for-each loops (notably this implies that a Lumiera iterator
** is used in the same position where usually the container would be referred).
** Yet, beyond that, Lumiera iterators can also be used in normal `for` loops
** and even in while loops, due to the bool-check to detect iteration end.
** A default constructed Lumiera iterator is always in _invalid state_
** and can be used as _sentinel value_ like the `end()` iterator.
** Yet, while Lumiera iterators can be copied and assigned, the effect of
** these operations is implementation defined; in some cases, taking a copy
** may allow to create a snapshot, while in other cases (especially when the
** Lumiera iterator uses an reference to tap into some remote service) all
** copies will refer to the same shared state. The rationale for this design
** was to simplify both building and using Lumiera Iterators; they are meant
** as an implementation tool, and not for creating external interfaces.
**
** However, there is a special provision for the latter use case, in the form
** of the lib::IterSource interface and framework; an `IterSource` embeds a
** classic OO interface with virtual methods, and thus allows to abstract the
** backing implementation entirely away.
**
** ## Concepts and Traits
**
** The »Lumiera Forward Iterators« do not map directly to any specification from
** the C++ standard. The concept std::input_iterator comes close, but a Lumiera
** iterator can not fulfil this concept, since it lacks the postfix increment.
** In the opposite direction, there is also no subsumption possible, since a
** STL iterator does not offer a bool-check to detect iteration end.
**
** Notably the header `lib/meta/trait.hpp` defines some detector traits, allowing
** to recognise if something is an iterable STL container, or a Lumiera iterator.
** Furthermore, lib::iter::Trait defines a transparent mechanism to extract the
** basic type definitions (`reference` ≙ what the iterator yields and
** `value_type` ≙ what to use to store results from an iterator)
**
** @see iter-adapter-test.cpp
** @see itertools.hpp
** @see IterSource (completely opaque iterator)
** @see value-type-binding.hpp
**
2009-07-11 19:23:20 +02:00
*/
#ifndef LIB_ITER_ADAPTER_H
#define LIB_ITER_ADAPTER_H
2009-07-11 19:23:20 +02:00
#include "lib/error.hpp"
#include "lib/meta/value-type-binding.hpp"
2009-07-11 19:23:20 +02:00
#include <iterator>
#include <limits>
2009-07-11 19:23:20 +02:00
namespace util { // see lib/util.hpp
template<class OBJ>
OBJ* unConst (const OBJ*);
template<class OBJ>
OBJ& unConst (OBJ const&);
}
2009-07-11 19:23:20 +02:00
namespace lib {
using std::is_pointer_v;
using std::remove_pointer_t;
using std::remove_reference_t;
2009-07-11 19:23:20 +02:00
namespace { // internal helpers
inline void
_throwIterExhausted()
{
throw lumiera::error::Invalid ("Can't iterate further",
lumiera::error::LUMIERA_ERROR_ITER_EXHAUST);
}
}
/** 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<ITER&&> (it); } \
friend ITER end (ITER const&) { return ITER(); } \
using iterator_category = std::input_iterator_tag; \
using difference_type = size_t;
/** define increment operator forwarding to baseclass but returning current */
#define LIFT_PARENT_INCREMENT_OPERATOR(_BASECLASS_)\
auto& \
operator++() \
{ \
_BASECLASS_::operator++(); \
return *this; \
}
2009-07-11 19:23:20 +02:00
namespace iter {
using meta::is_StateCore;
using meta::can_IterForEach;
using meta::can_STL_ForEach;
using meta::ValueTypeBinding;
using meta::RefTraits;
/** type binding helper: an iterato's actual result type */
template<class IT>
using Yield = decltype(std::declval<IT>().operator*());
/** the _result type_ yielded by a »state core« */
template<class COR>
using CoreYield = decltype(std::declval<COR>().yield());
template<class IT>
concept legacy_traits_marked = requires
{
typename std::iterator_traits<IT>::value_type;
};
template<class IT>
concept stl_iter = std::input_iterator<IT> or legacy_traits_marked<IT>;
/** Iterator-Traits: definitions to level the difference between
* Lumiera-Iterators and the STL usage of ranges / iterators.
*/
template<class IT>
struct Trait
{
static constexpr bool is_LumieraIter = can_IterForEach<IT>::value;
static constexpr bool is_STLIter = stl_iter<IT>;
static_assert(is_LumieraIter or is_STLIter, "source type must be an iterator");
using _ValTrait = std::conditional_t<is_LumieraIter, ValueTypeBinding<IT>
, RefTraits<std::iter_reference_t<IT>>>;
using value_type = _ValTrait::value_type;
using reference = _ValTrait::reference;
using pointer = _ValTrait::pointer;
};
}
2009-07-11 19:23:20 +02:00
/**
* 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 support `checkPoint()` 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
2009-11-01 02:02:21 +01:00
* @note it is possible to "hide" a smart-ptr within the CON template parameter.
*
* @tparam POS pointer or similar mutable link to the _current value_.
* Will be `bool()` checked to detect iteration end, end else dereferenced.
* @tparam CON type of the backing container, which needs to implement two
* extension point functions for iteration control
*
* \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 meta::ValueTypeBinding.
* -# 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
*
* @note
* - when POS is just a pointer, we use the pointee as value type
* - but when POS is a class, we expect the usual STL style nested typedefs
* `value_type`, `reference` and `pointer`
*
* @see scoped-ptrvect.hpp usage example
* @see value-type-binding.hpp
* @see iter-adapter-test.cpp
2009-07-11 19:23:20 +02:00
*/
template<class POS, class CON>
class IterAdapter
2009-07-11 19:23:20 +02:00
{
CON source_;
mutable POS pos_;
using _ValTrait = meta::ValueTypeBinding<std::remove_pointer_t<POS>>;
public:
using value_type = _ValTrait::value_type;
using reference = _ValTrait::reference;
using pointer = _ValTrait::pointer;
IterAdapter (CON src, POS const& startpos)
: source_(src)
, pos_(startpos)
{
check();
}
IterAdapter ()
: source_()
, pos_()
{ }
explicit
operator bool() const
{
return isValid();
}
/* === 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 not 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
{
2025-06-07 23:59:57 +02:00
return source_ and 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()
{
iterNext (source_,pos_); // extension point: free function iterNext(...)
check();
} // checkPoint() might mark end condition
// for comparison with IterAdapter{}
protected:
using ConRef = meta::RefTraits<CON>::reference;
/** allow derived classes to access backing container */
ConRef source() { return source_; }
const ConRef source() const { return unConst(this)->source_; }
void
resetPos (POS otherPos) ////////////////////////////////////////////TICKET #1125 : get rid of this function! it should not be there; rectify IterSource!
{
pos_ = otherPos;
check();
}
private:
void
_maybe_throw() const
{
if (not isValid())
_throwIterExhausted();
}
public:
ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (IterAdapter);
/// comparison is allowed to access impl iterator
template<class P1, class P2, class CX>
friend bool operator== (IterAdapter<P1,CX> const&, IterAdapter<P2,CX> const&);
2009-07-11 19:23:20 +02:00
};
/// Supporting equality comparisons...
template<class P1, class P2, class CON>
inline bool operator== (IterAdapter<P1,CON> const& il, IterAdapter<P2,CON> const& ir) { return il.pos_ == ir.pos_; }
template<class P1, class P2, class CON>
inline bool operator!= (IterAdapter<P1,CON> const& il, IterAdapter<P2,CON> const& ir) { return not (il == ir); }
/**
* Another Lumiera Forward Iterator building block, based on incorporating a state type
* as »*State Core*«, right into the iterator. Contrast this to IterAdapter, which refers to
* a managing container behind the scenes. To the contrary, here all of the state is assumed
* to live in the custom type embedded into this iterator, accessed and manipulated through
* a dedicated _iteration control API_ exposed as member functions.
*
* # Requirements for a »State Core«
* When building iterators with the help of IterStateWrapper or \ref IterableAdapter,
* it is assumed that the adapted _state core_ type represents a process of state evolution,
* which reaches a well defined end state eventually, but this end state is also the _bottom_
* - the core is default constructible
* - this default state represents the _bottom_ (final, invalid) state.
* - copyable, because iterators are passed by value; ideally also assignable
* - this type needs to provide an *iteration control API* with the following operations
* -# \c checkPoint establishes if the given state element represents a valid active state
* -# \c iterNext evolves this state by one step (sideeffect)
* -# \c yield realises the given state, yielding an element of _result type_ \a T
* @tparam ST type of the »state core«, defaults to T.
* The resulting iterator will hold an instance of ST, which thus
* needs to be copyable and default constructible to the extent
* this is required for the iterator as such.
* @tparam T (optional) result type, usually deduced from ST::yield
* @see IterableDecorator for variation of the same concept
* @see iter-explorer-test.hpp
* @see iter-adaptor-test.cpp
*/
template<class ST, typename T =iter::CoreYield<ST>>
class IterStateWrapper
{
ST core_;
public:
using value_type = meta::RefTraits<T>::value_type;
using reference = meta::RefTraits<T>::reference;
using pointer = meta::RefTraits<T>::pointer;
IterStateWrapper (ST&& initialState)
: core_(std::forward<ST>(initialState))
{ }
IterStateWrapper (ST const& initialState)
: core_(initialState)
{ }
IterStateWrapper ()
: core_()
{ }
explicit
operator bool() const
{
return isValid();
}
/* === lumiera forward iterator concept === */
T
operator*() const
{
__throw_if_empty();
return core_.yield(); // core interface: yield
}
pointer
operator->() const
{
if constexpr (meta::isLRef_v<T>)
{
__throw_if_empty();
return & core_.yield(); // core interface: yield
}
else
static_assert (!sizeof(T),
"can not provide operator-> "
"since iterator pipeline generates a value");
}
IterStateWrapper&
operator++()
{
__throw_if_empty();
core_.iterNext(); // core interface: iterNext
return *this;
}
bool
isValid () const
{
return core_.checkPoint(); // core interface: checkPoint
}
bool
empty () const
{
return not isValid();
}
protected:
/** allow derived classes to
* access state representation */
ST & stateCore() { return core_; }
ST const& stateCore() const { return core_; }
void
__throw_if_empty() const
{
if (not isValid())
_throwIterExhausted();
}
public:
ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (IterStateWrapper);
/// comparison is allowed to access state implementation core
template<class STX, class T1, class T2>
friend bool operator== (IterStateWrapper<STX,T1> const&, IterStateWrapper<STX,T2> const&);
};
/// Supporting equality comparisons of equivalent iterators (same state type)...
template<class ST, class T1, class T2>
inline bool
operator== (IterStateWrapper<ST,T1> const& il, IterStateWrapper<ST,T2> const& ir)
{
return (il.empty() and ir.empty())
or (il.isValid() and ir.isValid() and il.core_ == ir.core_);
}
template<class ST, class T1, class T2>
inline bool
operator!= (IterStateWrapper<ST,T1> const& il, IterStateWrapper<ST,T2> const& ir)
{
return not (il == ir);
}
/**
* Adapter to dress up an existing »Lumiera Forward Iterator« as »state core«.
* This building block achieves the complement of \ref IterStateWrapper by providing
* the API functions expected by the latter's _state protocol;_ a combination of
* IterStateCore and IterStateWrapper layered on top behaves identical to the
* original iterator. This can be used to change some aspects of the behaviour.
* @remark directly layered by inheritance, thus public functions of the
* wrapped iterator remain visible (contrary to IterStateWrapper)
*/
template<class IT>
class IterStateCore
: public IT
{
static_assert (iter::can_IterForEach<IT>::value
,"Lumiera Iterator required as source");
protected:
IT&
srcIter() const
{
return util::unConst(*this);
}
public:
using IT::IT;
/* === state protocol API for IterStateWrapper === */
bool
checkPoint() const
{
return bool(srcIter());
}
IT::reference
yield() const
{
return *srcIter();
}
void
iterNext()
{
++ srcIter();
}
};
/**
* Adapter to add sanity checks to a »state core«.
* @remark It is recommended to perform this kind of sanity checking by default,
* since the performance overhead is minute compared even to a virtual function
* call. However, there might be high-performance usage scenarios, where it is
* essential for the optimiser to be able to "strip every wart".
*/
template<class COR>
class CheckedCore
: public COR
{
static_assert (iter::is_StateCore<COR>::value
,"Adapted type must expose a »state core« API");
protected:
COR&
_rawCore() const
{
return util::unConst(*this);
}
void
__throw_if_empty() const
{
if (not checkPoint())
_throwIterExhausted();
}
public:
/** blindly pass-down any argument...
* @remark allows slicing move-initialisation from decorated
*/
template<typename...ARGS>
CheckedCore (ARGS&& ...init)
: COR(std::forward<ARGS>(init)...)
{ }
CheckedCore() =default;
CheckedCore (CheckedCore&&) =default;
CheckedCore (CheckedCore const&) =default;
CheckedCore& operator= (CheckedCore&&) =default;
CheckedCore& operator= (CheckedCore const&) =default;
using TAG_CheckedCore_Raw = COR; ///< marker to allow unwrapping the raw core
/* === state protocol API for IterStateWrapper === */
bool
checkPoint() const
{
return _rawCore().checkPoint();
}
decltype(auto)
yield() const
{
__throw_if_empty();
return _rawCore().yield();
}
void
iterNext()
{
__throw_if_empty();
_rawCore().iterNext();
}
};
/**
* Adapter to »piggy-back« a STL iterable container inline and expose it as »state core«.
* @warning be sure to understand the implications of this setup
* - when initialised by reference, the container's contents will be copied
* - when move-initialised, the container will be destroyed with this iterator
* - the container API remains visible (baseclass), which could confuse trait detection
*/
template<class CON>
class ContainerCore
: public CON
{
using Iter = CON::iterator;
Iter p_;
public:
ContainerCore (CON&& container)
: CON(std::forward<CON>(container))
, p_{CON::begin()}
{ }
// copy and assignment acceptable (warning!)
/* === »state core« protocol API === */
bool
checkPoint() const
{
return p_ != CON::end();
}
decltype(auto)
yield() const
{
return *p_;
}
void
iterNext()
{
++p_;
}
};
/**
* Decorator-Adapter to make a »*State Core*« iterable as Lumiera Forward Iterator.
* This is a fundamental (and low-level) building block and works essentially the
* same as IterStateWrapper — with the significant difference however that the
* _Core is mixed in by inheritance_ and thus its full interface remains publicly
* accessible. Another notable difference is that this adapter deliberately
* *performs no sanity-checks*. This can be dangerous, but allows to use this
* setup even in performance critical code.
* @warning be sure to understand the consequences of _using_ ´core.yield()´ without
* checks; an iterator built this way _must be checked_ before each use, unless
* it is guaranteed to be valid (by contextual knowledge). It might be a good idea
* to build some safety checks into the Core API functions instead, maybe even just
* as assertions, or to wrap the Core into \ref CheckedCore for most usages.
* @tparam COR type of the »state core«. The resulting iterator will _mix-in_ this type,
* and thus inherit properties like copy, move, compare, VTable, „POD-ness“.
* The COR must implement the following _iteration control API:_
* -# `checkPoint` establishes if the given state element represents a valid state
* -# ´iterNext` evolves this state by one step (sideeffect)
* -# `yield` realises the given state, exposing a result reference or value
* Furthermore, COR must be default-constructible in _disabled_ state
* @note the resulting iterator will attempt to yield a reference to type \a T when possible;
* but when the wrapped `COR::yield()` produces a value, this is passed as such, moreover
* #operator-> becomes disabled then, to prevent taking the address of the (temporary) value!
* @see IterExplorer a pipeline builder framework on top of IterableDecorator
* @see iter-explorer-test.hpp
* @see iter-adaptor-test.cpp
*/
template<class COR>
class IterableDecorator
: public COR
{
COR & _core() { return static_cast<COR&> (*this); }
COR const& _core() const { return static_cast<COR const&> (*this); }
protected:
void
__throw_if_empty() const
{
if (not isValid())
_throwIterExhausted();
}
public:
using YieldRes = iter::CoreYield<COR>;
using value_type = meta::RefTraits<YieldRes>::value_type;
using reference = meta::RefTraits<YieldRes>::reference;
using pointer = meta::RefTraits<YieldRes>::pointer;
/** by default, pass anything down for initialisation of the core.
* @note especially this allows move-initialisation from an existing core.
* @remarks to prevent this rule from "eating" the standard copy operations,
* and the no-op default ctor, we need to declare them explicitly below.
*/
template<typename...ARGS>
IterableDecorator (ARGS&& ...init)
: COR(std::forward<ARGS>(init)...)
{ }
IterableDecorator() =default;
IterableDecorator (IterableDecorator&&) =default;
IterableDecorator (IterableDecorator const&) =default;
IterableDecorator& operator= (IterableDecorator&&) =default;
IterableDecorator& operator= (IterableDecorator const&) =default;
/* === lumiera forward iterator concept === */
explicit operator bool() const { return isValid(); }
YieldRes
operator*() const
{
return _core().yield(); // core interface: yield
}
pointer
operator->() const
{
if constexpr (meta::isLRef_v<YieldRes>)
return & _core().yield(); // core interface: yield
else
static_assert (!sizeof(COR),
"can not provide operator-> "
"since iterator pipeline generates a value");
}
IterableDecorator&
operator++()
{
_core().iterNext(); // core interface: iterNext
return *this;
}
bool
isValid () const
{
return _core().checkPoint(); // core interface: checkPoint
}
bool
empty () const
{
return not isValid();
}
ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (IterableDecorator);
/// Supporting equality comparisons of equivalent iterators (equivalent state core)...
friend bool
operator== (IterableDecorator const& il, IterableDecorator const& ir)
{
return (il.empty() and ir.empty())
or (il.isValid() and ir.isValid() and il._core() == ir._core());
}
friend bool
operator!= (IterableDecorator const& il, IterableDecorator const& ir)
{
return not (il == ir);
}
};
/**
Library: a subtle fix to iterator value-type deduction After some investigation, I seem to understand the problem better now. This builds upon the Introduction of our `ValueTraits´ two years ago: e176e540043177 The root cause to these ongoing sporadic problems with type deduction seems to be that we encounter a **clash of concepts**: What Lumiera calls an »Iterator« is essentially different to the order of terms used by the STL. In the STL, an iterator can be seen as an ''abstracted pointer'', and is thus only meaningful in conjunction with a backing container. It is also this backing STL container that provides the value-type definitions. In Lumiera however, an ''Iterator is a promise to retrieve a sequence of evaluations''. For that reason, the Iterator itself provides value-type definitions, and the notion of "iterable" can largely be conflated with "Lumiera Iterator". Now, the `lib::RangeIter` can act as a bridge between both usage patterns, and thus it is possible to create a `RangeIter` both from a Lumiera Iterator or from a STL container. (As an aside, this is a similar idea as promoted by the "Range TS" in C++20 -- yet the Lumiera iterator concept is much more abstract and allows evaluations like tree expansion and search algorithms, which would be never possible with the STL based notion of iteration)... Thus the value type binding in `RangeIter` is the only correct place where to mitigate this ''clash of concepts:'' * if the `IT` parameter is a Lumiera iterator, we need to retrieve the type bindings defined therein * otherwise, we can assume that `IT` is an STL iterator and thus essentially a pointer, and we must strip that pointer and work from there.
2026-03-25 21:07:26 +01:00
* Access 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
* `pos==end`. Thus, a custom container may expose a range of
* elements of an embedded STL container, without controlling
Library: a subtle fix to iterator value-type deduction After some investigation, I seem to understand the problem better now. This builds upon the Introduction of our `ValueTraits´ two years ago: e176e540043177 The root cause to these ongoing sporadic problems with type deduction seems to be that we encounter a **clash of concepts**: What Lumiera calls an »Iterator« is essentially different to the order of terms used by the STL. In the STL, an iterator can be seen as an ''abstracted pointer'', and is thus only meaningful in conjunction with a backing container. It is also this backing STL container that provides the value-type definitions. In Lumiera however, an ''Iterator is a promise to retrieve a sequence of evaluations''. For that reason, the Iterator itself provides value-type definitions, and the notion of "iterable" can largely be conflated with "Lumiera Iterator". Now, the `lib::RangeIter` can act as a bridge between both usage patterns, and thus it is possible to create a `RangeIter` both from a Lumiera Iterator or from a STL container. (As an aside, this is a similar idea as promoted by the "Range TS" in C++20 -- yet the Lumiera iterator concept is much more abstract and allows evaluations like tree expansion and search algorithms, which would be never possible with the STL based notion of iteration)... Thus the value type binding in `RangeIter` is the only correct place where to mitigate this ''clash of concepts:'' * if the `IT` parameter is a Lumiera iterator, we need to retrieve the type bindings defined therein * otherwise, we can assume that `IT` is an STL iterator and thus essentially a pointer, and we must strip that pointer and work from there.
2026-03-25 21:07:26 +01:00
* the details of the iteration (as would be possible using the
* more generic IterAdapter).
* @note
* - when IT is a STL iterator, we rely on the iterator concepts and
* especially on `std::iter_reference_t` to find the `reference_type`
Library: a subtle fix to iterator value-type deduction After some investigation, I seem to understand the problem better now. This builds upon the Introduction of our `ValueTraits´ two years ago: e176e540043177 The root cause to these ongoing sporadic problems with type deduction seems to be that we encounter a **clash of concepts**: What Lumiera calls an »Iterator« is essentially different to the order of terms used by the STL. In the STL, an iterator can be seen as an ''abstracted pointer'', and is thus only meaningful in conjunction with a backing container. It is also this backing STL container that provides the value-type definitions. In Lumiera however, an ''Iterator is a promise to retrieve a sequence of evaluations''. For that reason, the Iterator itself provides value-type definitions, and the notion of "iterable" can largely be conflated with "Lumiera Iterator". Now, the `lib::RangeIter` can act as a bridge between both usage patterns, and thus it is possible to create a `RangeIter` both from a Lumiera Iterator or from a STL container. (As an aside, this is a similar idea as promoted by the "Range TS" in C++20 -- yet the Lumiera iterator concept is much more abstract and allows evaluations like tree expansion and search algorithms, which would be never possible with the STL based notion of iteration)... Thus the value type binding in `RangeIter` is the only correct place where to mitigate this ''clash of concepts:'' * if the `IT` parameter is a Lumiera iterator, we need to retrieve the type bindings defined therein * otherwise, we can assume that `IT` is an STL iterator and thus essentially a pointer, and we must strip that pointer and work from there.
2026-03-25 21:07:26 +01:00
* - but when IT is a »Lumiera Forward Iterator«, we need to evaluate
* nested typedefs `value_type`, `reference` and `pointer`
* @remark the reason for this tricky distinction is a _clash of concepts:_
* In Lumiera, »Iterator« has a special meaning and can used on itself,
* while in the STL, an »Iterator« is a pointer in disguise and is
* only meaningful when the backing container is known. This adaptor,
* RangeIter, acts as a bridge and levels those usage styles.
*/
template<class IT>
class RangeIter
{
IT p_;
IT e_;
public:
using pointer = iter::Trait<IT>::pointer;
using reference = iter::Trait<IT>::reference;
using value_type = iter::Trait<IT>::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<class I2>
RangeIter (I2 const& oIter)
: p_(oIter.getPos())
, e_(oIter.getEnd())
{ }
explicit
operator bool() const
{
return isValid();
}
/* === 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
{
2025-06-07 23:59:57 +02:00
return (p_!= IT()) and (p_ != e_);
}
bool
empty () const
{
return not isValid();
}
/** access wrapped STL iterator */
const IT& getPos() const { return p_; }
const IT& getEnd() const { return e_; }
ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (RangeIter);
private:
void
_maybe_throw() const
{
if (!isValid())
_throwIterExhausted();
}
};
/// Supporting equality comparisons...
template<class I1, class I2>
2025-06-07 23:59:57 +02:00
inline bool operator== (RangeIter<I1> const& il, RangeIter<I2> const& ir) { return (!il and !ir) or (il.getPos() == ir.getPos()); }
template<class I1, class I2>
2025-06-07 23:59:57 +02:00
inline bool operator!= (RangeIter<I1> const& il, RangeIter<I2> const& ir) { return not (il == ir); }
/**
* Enumerate all "numbers" within a range.
* This allows to build pipelines based on all
* numbers "for `i` from `1...N`". This range is _half open_,
* i.e. the start is inclusive and the end point is exclusive.
* @remark default constructed iters are empty and compare equal with
* any other exhausted NumIter; essential requirement for a
* Lumiera Forward Iterator (allows use in range-for-loops).
* @tparam INT a number like type, which can be incremented and compared.
* @note deliberately yields by-value and has no `operator->`
*/
template<typename INT>
class NumIter
{
INT i_;
INT e_;
public:
2025-06-07 23:59:57 +02:00
using pointer = INT*;
using reference = INT&;
using value_type = INT ;
NumIter (INT start, INT end)
: i_(start)
, e_(end)
{ }
template<typename X>
NumIter (X&& start, X&& end)
: i_(std::forward<X>(start))
, e_(std::forward<X>(end))
{ }
NumIter ()
: i_()
, e_()
{ }
// standard copy operations acceptable
explicit
operator bool() const
{
return isValid();
}
/* === lumiera forward iterator concept === */
value_type
operator*() const
{
_maybe_throw();
return i_;
}
NumIter&
operator++()
{
_maybe_throw();
++i_;
return *this;
}
bool
isValid () const
{
Library: investigate how a »zip iterator« can be built Basically I am sick of writing for-loops in those cases where the actual iteration is based on one or several data sources, and I just need some damn index counter. Nothing against for-loops in general — they have their valid uses — sometimes a for-loop is KISS But in these typical cases, an iterator-based solution would be a one-liner, when also exploiting the structured bindings of C++17 ''I must admit that I want this for a loooooong time —'' ...but always got intimidated again when thinking through the fine points. Basically it „should be dead simple“ — as they say Well — — it ''is'' simple, after getting the nasty aspects of tuple binding and reference data types out of the way. Yesterday, while writing those `TestFrame` test cases (which are again an example where you want to iterate over two word sequences simultaneously and just compare them), I noticed that last year I learned about the `std::apply`-to-fold-expression trick, and that this solution pattern could be adapted to construct a tuple directly, thereby circumventing most of the problems related to ''perfect forwarding'' So now we have a new util function `mapEach` (defined in `tuple-helper.hpp`) and I have learned how to make this application completely generic. As a second step, I implemented a proof-of-concept in `IterZip_test`, which indeed was not really challenging, because the `IterExplorer` is so very sophisticated by now and handles most cases with transparent type-driven adaptors. A lot of work went into `IterExplorer` over the years, and this pays off now. The solution works as follows: * apply the `lib::explore()` constructor function to the varargs * package the resulting `IterExplorer` instantiations into a tuple * build a »state core« implementation which just lifts out the three iterator primitives onto this ''product type'' (i.e. the tuple) * wrap it in yet another `IterExplorer` * add a transformer function on top to extract a value-tuple for each ''yield' As expected, works out-of-the-box, with all conceivable variants and wild mixes of iterators, const, pointers, references, you name it.... PS: I changed the rendering of unsigned types in diagnostic output to use the short notation, e.g. `uint` instead of `unsigned int`. This dramatically improves the legibility of verification strings.
2024-11-21 23:30:07 +01:00
return i_ < e_; // NOTE: use comparison to detect iteration end
}
bool
empty () const
{
return not isValid();
}
/** access wrapped index elements */
INT& getPos() const { return i_; }
INT& getEnd() const { return e_; }
ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (NumIter);
/// Supporting equality comparisons...
bool operator!= (NumIter const& o) const { return not operator==(o); }
bool operator== (NumIter const& o) const { return (empty() and o.empty()) // empty iters must be equal (Lumiera iter requirement)
or (i_ == o.i_ and e_ == o.e_); }
private:
void
_maybe_throw() const
{
if (!isValid())
_throwIterExhausted();
}
};
/** convenience function to iterate "each number" */
template<typename INT>
inline NumIter<INT>
eachNum (INT start = std::numeric_limits<INT>::min()
,INT end = std::numeric_limits<INT>::max())
{
return NumIter<INT> (start, end);
}
/**
* Helper for type rewritings:
* build a similar iterator with another value type
*/
template<class TY>
struct IterRebind;
template<template<class,class> class Iter, class TY, class CON>
struct IterRebind<Iter<TY,CON>>
{
2025-06-07 23:59:57 +02:00
using Container = CON;
using ElemType = TY ;
template<class T2>
struct SimilarIter ///< rebind to a similarly structured Iterator with value type T2
{
2025-06-07 23:59:57 +02:00
using Type = Iter<T2,CON>;
};
};
template<class IT>
struct IterRebind<RangeIter<IT>>
: IterRebind<IT>
{
template<class T2>
struct SimilarIter ///< rebind to rewritten Iterator wrapped into RangeIter
{
using WrappedIter = IterRebind<IT>::template SimilarIter<T2>::Type;
2025-06-07 23:59:57 +02:00
using Type = RangeIter<WrappedIter>;
};
};
/** wrapper to expose values as const */
template<class IT>
class ConstIter
{
IT i_; ///< nested source iterator
public:
using value_type = const IT::value_type;
using pointer = const IT::pointer ;
using reference = const IT::reference ;
ConstIter (IT srcIter)
: i_(srcIter)
{ }
explicit
operator bool() const
{
return isValid();
}
/* === 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 not isValid();
}
/** access the wrapped implementation iterator */
IT const&
getBase() const
{
return i_;
}
ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (ConstIter);
};
/// Supporting equality comparisons...
template<class I1, class I2>
inline bool operator== (ConstIter<I1> const& il, ConstIter<I2> const& ir) { return il.getBase() == ir.getBase(); }
template<class I1, class I2>
inline bool operator!= (ConstIter<I1> const& il, ConstIter<I2> const& ir) { return not (il == ir); }
}// namespace lib
#endif /*LIB_ITER_ADAPTER_H*/