lumiera_documentation/src/lib/iter-tree-explorer.hpp
Ichthyostega ce1ee71955 TreeExplorer: clarify base initialisation
as it turned out, when "inheriting" ctors, C++14 removes the base classes' copy ctors.
C++17 will rectify that. Thus for now we need to define explicitly that
we'll accept the base for initialising the derived. But we need do so
only on one location, namely the most down in the chain.
2017-12-08 05:32:04 +01:00

908 lines
35 KiB
C++

/*
ITER-TREE-EXPLORER.hpp - building blocks for iterator evaluation strategies
Copyright (C) Lumiera.org
2017, Hermann Vosseler <Ichthyostega@web.de>
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-tree-explorer.hpp
** Building tree expanding and backtracking evaluations within hierarchical scopes.
** Based on the *Lumiera Forward Iterator* concept and using the basic IterAdaptor templates,
** these components allow to implement typical evaluation strategies, like conditional expanding
** or depth-first exploration of a hierarchical structure. Since the access to this structure is
** abstracted through the underlying iterator, what we effectively get is a functional datastructure.
** The implementation is based on the idea of a "state core", which is wrapped right into the iterator
** itself (value semantics) -- similar to the IterStateWrapper, which is one of the basic helper templates
** provided by iter-adapter.hpp.
**
** @remark as of 2017, this template, as well as the initial IterExplorer (draft from 2012) can be
** seen as steps towards designing a framework of building blocks for tree expanding and
** backtracking algorithms. Due to the nature of Lumiera's design, we repeatedly encounter
** this kind of computation pattern, when it comes to matching flexible configuration against
** a likewise hierarchical and rules based model. To keep the code base maintainable,
** we deem it crucial to reduce the inherent complexity in such algorithms by clearly
** separate the _mechanics of evaluation_ from the actual logic of the target domain.
**
** # Iterators as Monad
** The fundamental idea behind the implementation technique used here is the _Monad pattern_
** known from functional programming. A Monad is a container holding some arbitrarily typed base value; the monad can
** be seen as "amplifying" and enhancing the contained base value by attaching additional properties or capabilities
** This is a rather detached concept with a wide variety of applications (things like IO state, parsers, combinators,
** calculations with exception handling but also simple data structures like lists or trees). The key point with any
** monad is the ability to _bind a function_ into the monad; this function will work on the _contained base values_
** and produce a modified new monad instance. In the simple case of a list, "binding" a function basically means
** to _map the function onto_ the elements in the list.
**
** ## Rationale
** The primary benefit of using the monad pattern is to separate the transforming operation completely from
** the mechanics of applying that operation and combining the results. More specifically, we rely on an iterator
** to represent an abstracted source of data and we expose the combined and transformed results again as such
** an abstracted data sequence. While the transformation to apply can be selected at runtime (as a functor),
** the monad pattern defines a sane way to represent partial evaluation state without requiring a container
** for intermediary results. This is especially helpful when
** - a flexible and unspecific source data structure needs to be processed
** - and this evaluation needs to be done asynchronously and in parallel (no locking, immutable data)
** - and a partial evaluation needs to be stored as continuation (not relying on the stack for partial results)
**
** # A Pipeline builder
** Based on such concepts, structures and evaluation patterns, the TreeExplorer serves the purpose to provide
** building blocks to assemble a _processing pipeline_, where processing will happen _on demand,_ while iterating.
** TreeExplorer itself is both a Lumiera Forward Iterator based on some wrapped data source, and at the same time
** it is a builder to chain up processing steps to work on the data pulled from that source. These processing steps
** are attached as _decorators_ wrapping the source, in the order the corresponding builder functions were invoked.
** - the _expand operation_ installs a function to consume one element and replace it by the sequence of elements
** (``children'') produced by that _»expansion functor«_. But this expansion does not happen automatically and
** on each element, rather it is triggered by issuing a dedicated `expandChildren()` call on the processing
** pipeline. Thus, binding the expansion functor has augmented the data source with the ability to explore
** some part in more detail _when required.
** - the _transform operation_ installs a function to be mapped onto each element retrieved from the underlying source
** - in a similar vein, the _filter operation_ binds a predicate to decide about using or discarding data
** - in concert, expand- and transform operation allow to build hierarchy evaluation algorithms without exposing
** any knowledge regarding the concrete hierarchy used and explored as data source.
**
** @todo WIP-WIP-WIP initial draft as of 11/2017
**
** @see IterTreeExplorer_test
** @see iter-adapter.hpp
** @see itertools.hpp
** @see IterSource (completely opaque iterator)
**
*/
#ifndef LIB_ITER_TREE_EXPLORER_H
#define LIB_ITER_TREE_EXPLORER_H
#include "lib/error.hpp"
#include "lib/meta/duck-detector.hpp"
#include "lib/meta/function.hpp"
#include "lib/meta/trait.hpp"
#include "lib/wrapper.hpp" ////////////TODO : could be more lightweight by splitting FunctionResult into separate header. Relevant?
#include "lib/iter-adapter.hpp"
#include "lib/iter-source.hpp" /////////////TICKET #493 : only using the IterSource base feature / interface here. Should really split the iter-source.hpp
#include "lib/iter-stack.hpp"
#include "lib/util.hpp"
#include "lib/test/test-helper.hpp"///////////TODO Bug-o
#include <functional>
#include <utility>
namespace lib {
using std::move;
using std::forward;
using std::function;
using util::isnil;
namespace iter_explorer {
template<class CON>
using iterator = typename meta::Strip<CON>::TypeReferred::iterator;
template<class CON>
using const_iterator = typename meta::Strip<CON>::TypeReferred::const_iterator;
template<class CON>
struct StlRange
: RangeIter<iterator<CON>>
{
StlRange() =default;
StlRange (CON& container)
: RangeIter<iterator<CON>> {begin(container), end(container)}
{ }
// standard copy operations acceptable
};
template<class CON>
struct StlRange<const CON>
: RangeIter<const_iterator<CON>>
{
StlRange() =default;
StlRange (CON const& container)
: RangeIter<const_iterator<CON>> {begin(container), end(container)}
{ }
// standard copy operations acceptable
};
/**
* Decorate a state or logic core to treat it as Lumiera Forward Iterator.
* This Adapter does essentially the same as \ref IterStateWrapper, but here
* the state core is not encapsulated opaque, but rather inherited, and thus
* the full interface of the core remains publicly accessible.
*/
template<typename T, class COR>
class IterableDecorator
: public COR
{
COR & _core() { return static_cast<COR&> (*this); }
COR const& _core() const { return static_cast<COR const&> (*this); }
void
__throw_if_empty() const
{
if (not isValid())
throw lumiera::error::Invalid ("Can't iterate further",
lumiera::error::LUMIERA_ERROR_ITER_EXHAUST);
}
public:
typedef T* pointer;
typedef T& reference;
typedef T value_type;
/** 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;
explicit operator bool() const { return isValid(); }
/* === lumiera forward iterator concept === */
reference
operator*() const
{
__throw_if_empty();
return _core().yield (); // core interface: yield
}
pointer
operator->() const
{
__throw_if_empty();
return & _core().yield(); // core interface: yield
}
IterableDecorator&
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();
}
ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (IterableDecorator);
/// Supporting equality comparisons of equivalent iterators (same state core)...
template<class T1, class T2>
friend bool
operator== (IterableDecorator<T1,COR> const& il, IterableDecorator<T2,COR> const& ir)
{
return (il.empty() and ir.empty())
or (il.isValid() and ir.isValid() and il._core() == ir._core());
}
template<class T1, class T2>
friend bool
operator!= (IterableDecorator<T1,COR> const& il, IterableDecorator<T2,COR> const& ir)
{
return not (il == ir);
}
};
}//(End) namespace iter_explorer : predefined policies and configurations
namespace { // TreeExplorer traits
using meta::enable_if;
using meta::disable_if;
using meta::Yes_t;
using meta::No_t;
using meta::_Fun;
using std::__and_;
using std::__not_;
using std::is_convertible;
using std::remove_reference_t;
using meta::can_IterForEach;
using meta::can_STL_ForEach;
META_DETECT_FUNCTION_ARGLESS(checkPoint);
META_DETECT_FUNCTION_ARGLESS(iterNext);
META_DETECT_FUNCTION_ARGLESS(yield);
template<class SRC>
struct is_StateCore
: __and_< HasArglessFun_checkPoint<SRC>
, HasArglessFun_iterNext<SRC>
, HasArglessFun_yield<SRC>
>
{ };
template<class SRC>
struct shall_wrap_STL_Iter
: __and_<can_STL_ForEach<SRC>
,__not_<can_IterForEach<SRC>>
>
{ };
template<class SRC>
struct shall_use_StateCore
: __and_<__not_<can_IterForEach<SRC>>
,is_StateCore<SRC>
>
{ };
template<class SRC>
struct shall_use_Lumiera_Iter
: __and_<can_IterForEach<SRC>
,__not_<is_StateCore<SRC>>
>
{ };
/** the _value type_ yielded by a »state core« */
template<class COR>
struct CoreYield
{
using Res = remove_reference_t<decltype(std::declval<COR>().yield())>;
using value_type = typename meta::TypeBinding<Res>::value_type;
using reference = typename meta::TypeBinding<Res>::reference;
using pointer = typename meta::TypeBinding<Res>::pointer;
};
/** decide how to adapt and embed the source sequence into the resulting TreeExplorer */
template<class SRC, typename SEL=void>
struct _DecoratorTraits
{
static_assert (!sizeof(SRC), "Can not build TreeExplorer: Unable to figure out how to iterate the given SRC type.");
};
template<class SRC>
struct _DecoratorTraits<SRC, enable_if<is_StateCore<SRC>>>
{
using SrcVal = typename CoreYield<SRC>::value_type;
using SrcIter = iter_explorer::IterableDecorator<SrcVal, SRC>;
};
template<class SRC>
struct _DecoratorTraits<SRC, enable_if<shall_use_Lumiera_Iter<SRC>>>
{
using SrcIter = remove_reference_t<SRC>;
using SrcVal = typename SrcIter::value_type;
};
template<class SRC>
struct _DecoratorTraits<SRC, enable_if<shall_wrap_STL_Iter<SRC>>>
{
static_assert (not std::is_rvalue_reference<SRC>::value,
"container needs to exist elsewhere during the lifetime of the iteration");
using SrcIter = iter_explorer::StlRange<SRC>;
using SrcVal = typename SrcIter::value_type;
};
/**
* @internal technical details of binding a functor into the TreeExplorer.
* Notably, this happens when adapting an _"expansion functor"_ to allow expanding a given element
* from the TreeExploer (iterator) into a sequence of child elements. A quite similar situation
* arises when binding a _transformation function_ to be mapped onto each result element.
*
* The TreeExplorer::expand() operation accepts various flavours of functors, and depending on
* the signature of such a functor, an appropriate adapter will be constructed here, allowing to
* write a generic Expander::expandChildren() operation. The following details are handled here:
* - detect if the passed functor is generic, or a regular "function-like" entity.
* - in case it is generic (generic lambda), we assume it actually accepts a reference to
* the source iterator type `SRC`. Thus we instantiate a templated functor with this
* argument type to find out about its result type (and this instantiation may fail)
* - moreover, we try to determine, if an explicitly typed functor accepts a value of the
* embedded source iterator (this is the "monadic" usage pattern), or if it rather accepts
* the iterator or state core itself (the"opaque state manipulation" usage pattern).
* - we generate a suitable argument accessor function and build the function composition
* of this accessor and the provided _expansion functor_.
* - the resulting, combined functor is stored into a std::function, but wired in a way to
* keep the argument-accepting front-end still generic (templated `operator()`). This
* special adapter supports the case when the _expansion functor_ yields a child sequence
* type different but compatible to the original source sequence embedded in TreeExplorer.
* @tparam FUN something _"function-like"_ passed as functor to be bound
* @tparam SRC the source iterator type to apply when attempting to use a generic lambda as functor
*/
template<class FUN, typename SRC>
struct _BoundFunctorTraits
{
/** handle all regular "function-like" entities */
template<typename F, typename SEL =void>
struct FunDetector
{
using Sig = typename _Fun<F>::Sig;
};
/** handle a generic lambda, accepting a reference to the `SRC` iterator */
template<typename F>
struct FunDetector<F, disable_if<_Fun<F>> >
{
using Arg = typename std::add_lvalue_reference<SRC>::type;
using Ret = decltype(std::declval<F>() (std::declval<Arg>()));
using Sig = Ret(Arg);
};
using Sig = typename FunDetector<FUN>::Sig;
using Arg = typename _Fun<Sig>::Args::List::Head; // assuming function with a single argument
using Res = typename _Fun<Sig>::Ret;
/** adapt to a functor, which accesses the source iterator or embedded "state core" */
template<class ARG, class SEL =void>
struct ArgAccessor
{
using FunArgType = remove_reference_t<Arg>;
static_assert (std::is_convertible<ARG, FunArgType>::value,
"the expansion functor must accept the source iterator or state core as parameter");
static auto build() { return [](ARG& arg) -> ARG& { return arg; }; }
};
/** adapt to a functor, which accepts the value type of the source sequence ("monadic" usage pattern) */
template<class IT>
struct ArgAccessor<IT, enable_if<is_convertible<typename IT::value_type, Arg>>>
{
static auto build() { return [](auto& iter) { return *iter; }; }
};
/** holder for the suitably adapted _expansion functor_ */
struct Functor
{
function<Sig> boundFunction;
template<typename ARG>
Res
operator() (ARG& arg)
{
auto accessArg = ArgAccessor<ARG>::build();
return boundFunction (accessArg (arg));
}
};
};
}//(End) TreeExplorer traits
namespace iter_explorer {
/**
* @internal Base of pipe processing decorator chain.
* TreeExplorer allows to create a stack out of various decorating processors
* - each decorator is itself a _"state core"_, adding some on-demand processing
* - each wraps and adapts a source iterator, attaching to and passing-on the iteration logic
* Typically each such layer is configured with actual functionality provided as lambda or functor.
* Yet in addition to forming an iteration pipeline, there is kind of an internal interconnection
* protocol, allowing the layers to collaborate; notably this allows to handle an expandChildren()
* call, where some "expansion layer" consumes the current element and replaces it by an expanded
* series of new elements. Other layers might need to sync to this operation, and thus it is passed
* down the chain. For that reason, we need a dedicated BaseAdapter to adsorb such chained calls.
* @remark when building the TreeExplorer, the to-be-wrapped source is fed down into its place
* within BaseAdapter. For that reason, we need to lift the copy ctors of the base.
* Just inheriting the base class ctors won't do that, at least not in C++14.
*/
template<class SRC>
struct BaseAdapter
: SRC
{
BaseAdapter() = default;
BaseAdapter(SRC const& src) : SRC(src) { }
BaseAdapter(SRC && src) : SRC(src) { }
void expandChildren() { }
};
/**
* @internal Decorator for TreeExplorer adding the ability to "expand children".
* The expandChildren() operation is the key element of a depth-first evaluation: it consumes
* one element and performs a preconfigured _expansion functor_ on that element to yield
* its "children". These are given in the form of another iterator, which needs to be
* compatible to the source iterator ("compatibility" boils down to both iterators
* yielding a compatible value type). Now, this _sequence of children_ effectively
* replaces the expanded source element in the overall resulting sequence; which
* means, the nested sequence was _flattened_ into the results. Since this expand()
* operation can again invoked on the results, the implementation of such an evaluation
* requires a stack datastructure, so the nested iterator from each expand() invocation
* can be pushed to become the new active source for iteration. Thus the primary purpose
* of this Expander (decorator) is to integrate those "nested child iterators" seamlessly
* into the overall iteration process; once a child iterator is exhausted, it will be
* popped and iteration continues with the previous child iterator or finally with
* the source iterator wrapped by this decorator.
* @remark since we allow a lot of leeway regarding the actual form and definition of the
* _expansion functor_, there is a lot of minute technical details, mostly confined
* within the _BoundFunctorTraits.
* @tparam SRC the wrapped source iterator, typically a TreeExplorer or nested decorator.
* @tparam FUN the concrete type of the functor passed. Will be dissected to find the signature
*/
template<class SRC, class FUN>
class Expander
: public SRC
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
using _Traits = _BoundFunctorTraits<FUN,SRC>;
using ExpandFunctor = typename _Traits::Functor;
using ResIter = typename _DecoratorTraits<typename _Traits::Res>::SrcIter;
static_assert (std::is_convertible<typename ResIter::value_type, typename SRC::value_type>::value,
"the iterator from the expansion must yield compatible values");
ExpandFunctor expandChildren_;
IterStack<ResIter> expansions_;
public:
Expander() =default;
// inherited default copy operations
Expander (SRC&& parentExplorer, FUN&& expandFunctor)
: SRC{move (parentExplorer)} // NOTE: slicing move to strip TreeExplorer (Builder)
, expandChildren_{forward<FUN> (expandFunctor)}
, expansions_{}
{ }
/** core operation: expand current head element */
void
expandChildren()
{
REQUIRE (this->checkPoint(), "attempt to expand an empty explorer");
ResIter expanded{ 0 < depth()? expandChildren_(*expansions_)
: expandChildren_(*this)};
iterNext(); // consume current head element
if (not isnil(expanded))
expansions_.push (move(expanded));
SRC::expandChildren();
}
/** diagnostics: current level of nested child expansion */
size_t
depth() const
{
return expansions_.size();
}
public: /* === Iteration control API for IterableDecorator === */
bool
checkPoint() const
{
return 0 < depth()
or this->isValid();
}
typename SRC::reference
yield() const
{
return 0 < depth()? **expansions_
: **this;
}
void
iterNext()
{
if (0 < depth())
{
++(*expansions_);
while (0 < depth() and not *expansions_)
++expansions_;
}
else
++(*this);
}
};
/**
* @internal Decorator for TreeExplorer to map a transformation function on all results.
* The transformation function is invoked on demand, and only once per item to be treated,
* storing the treated result into an universal value holder buffer. The given functor
* is adapted in a similar way as the "expand functor", so to detect and convert the
* expected input on invocation.
*/
template<class SRC, class FUN>
class Transformer
: public SRC
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
using _Traits = _BoundFunctorTraits<FUN,SRC>;
using Res = typename _Traits::Res;
using TransformFunctor = typename _Traits::Functor;
using TransformedItem = wrapper::ItemWrapper<Res>;
TransformFunctor trafo_;
TransformedItem treated_;
public:
using value_type = typename meta::TypeBinding<Res>::value_type;
using reference = typename meta::TypeBinding<Res>::reference;
using pointer = typename meta::TypeBinding<Res>::pointer;
Transformer() =default;
// inherited default copy operations
Transformer (SRC&& dataSrc, FUN&& transformFunctor)
: SRC{move (dataSrc)}
, trafo_{forward<FUN> (transformFunctor)}
{ }
/** refresh state when other layers manipulate the source sequence
* @remark expansion replaces the current element by a sequence of
* "child" elements. Since we cache our transformation, we
* need to ensure possibly new source elements get processed
*/
void
expandChildren()
{
treated_.reset();
SRC::expandChildren();
}
public: /* === Iteration control API for IterableDecorator === */
bool
checkPoint() const
{
return bool(srcIter());
}
reference
yield() const
{
return unConst(this)->invokeTransformation();
}
void
iterNext()
{
++ srcIter();
treated_.reset();
}
private:
SRC&
srcIter() const
{
return unConst(*this);
}
reference
invokeTransformation ()
{
if (not treated_) // invoke transform function once per src item
treated_ = trafo_(srcIter());
return *treated_;
}
};
/**
* @internal Decorator for TreeExplorer to filter elements based on a predicate.
* Similar to the Transformer, the given functor is adapted as appropriate. However,
* we require the functor's result type to be convertible to bool, to serve as approval test.
* The filter predicate and thus the source iterator is evaluated _eagerly_, to establish the
* *invariant* of this class: _if a "current element" exists, it has already been approved._
*/
template<class SRC, class FUN>
class Filter
: public SRC
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
using _Traits = _BoundFunctorTraits<FUN,SRC>;
using Res = typename _Traits::Res;
static_assert(std::is_constructible<bool, Res>::value, "Functor must be a predicate");
using FilterPredicate = typename _Traits::Functor;
FilterPredicate predicate_;
public:
Filter() =default;
// inherited default copy operations
Filter (SRC&& dataSrc, FUN&& filterFun)
: SRC{move (dataSrc)}
, predicate_{forward<FUN> (filterFun)}
{
pullFilter(); // initially pull to establish the invariant
}
/** refresh state when other layers manipulate the source sequence.
* @note possibly pulls to re-establish the invariant */
void
expandChildren()
{
pullFilter();
SRC::expandChildren();
}
public: /* === Iteration control API for IterableDecorator === */
bool
checkPoint() const
{
return bool(srcIter());
}
typename SRC::reference
yield() const
{
return *srcIter();
}
void
iterNext()
{
++ srcIter();
pullFilter();
}
private:
SRC&
srcIter() const
{
return unConst(*this);
}
/** @note establishes the invariant:
* whatever the source yields as current element,
* has already been approved by our predicate */
void
pullFilter ()
{
while (srcIter() and not predicate_(srcIter()))
++srcIter();
}
};
}
/**
* @todo WIP 12/2017
*/
template<typename VAL>
struct IterExploreSource
: IterSource<VAL>::iterator
{
//////TODO clarify allowed ctors
void
expandChildren()
{
UNIMPLEMENTED ("how to phone home...");
}
private:
};
/**
* Adapter to build a demand-driven tree expanding and exploring computation
* based on a custom opaque _state core_. TreeExploer adheres to the _Monad_
* pattern known from functional programming, insofar the _expansion step_ is
* tied into the basic template by means of a function provided at usage site.
* This allows to separate the mechanics of evaluation and result combination
* from the actual processing and thus to define tree structured computations
* based on a not further disclosed, opaque source data structure.
*
* @todo WIP -- preliminary draft as of 11/2017
*/
template<class SRC>
class TreeExplorer
: public SRC
{
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
public:
using value_type = typename meta::TypeBinding<SRC>::value_type;
using reference = typename meta::TypeBinding<SRC>::reference;
using pointer = typename meta::TypeBinding<SRC>::pointer;
/** pass-through ctor */
using SRC::SRC;
/* ==== Builder functions ==== */
/** preconfigure this TreeExplorer to allow for _"expansion of children"_.
* The resulting iterator exposes an `expand()` function, which consumes
* the current head element of this iterator and feeds it through the
* _expansion functor_, which was provided to this builder function here.
* The _expansion functor_ is expected to yield a sequence of "child" elements,
* which will be integrated into the overall result sequence instead of the
* consumed source element. Thus, repeatedly invoking `expand()` until exhaustion
* generates a _depth-first evaluation_, since every child will be expanded until
* reaching the leaf nodes of a tree like structure.
*
* @param expandFunctor a "function-like" entity to perform the actual "expansion".
* There are two distinct usage patterns, as determined by the signature
* of the provided function or functor:
* - _"monad style"_: the functor takes a _value_ from the sequence and
* produces a new sequence, iterator or collection of compatible values
* - _"opaque state manipulation"_: the functor accepts the concrete source
* iterator type, or even a "state core" type embedded therein. It yields
* a new sequence, state core or collection representing the "children".
* Obviously, the intention here is to allow hidden collaboration between
* the expansion functor and the embedded opaque "data source". For that
* reason, the functor may take its argument by reference, and a produced
* new "child state core" may likewise collaborate with that original
* data source or state core behind the scenes; the latter is guaranteed
* to exist during the whole lifetime of this TreeExplorer.
* @note there is limited support for generic lambdas, but only for the second case.
* The reason is, we can not "probe" a template or generic lambda for possible
* argument and result types. Thus, if you provide a generic lambda, TreeExplorer
* tries to pass it a `SrcIter &` (reference to the embedded original iterator).
* For any other cases, please provide a lambda or functor with a single, explicitly
* typed argument. Obviously, argument and result type should also make sense for
* the desired evaluation pattern, otherwise you'll get all kinds of nasty
* template compilation failures (have fun!)
*/
template<class FUN>
auto
expand (FUN&& expandFunctor)
{
using ResCore = iter_explorer::Expander<SRC, FUN>;
using ResIter = typename _DecoratorTraits<ResCore>::SrcIter;
return TreeExplorer<ResIter> (ResCore {move(*this), forward<FUN>(expandFunctor)});
}
/** adapt this TreeExplorer to pipe each result value through a transformation function.
* Several "layers" of mapping can be piled on top of each other, possibly mixed with the
* other types of adaptation, like the child-expanding operation, or a filter. Obviously,
* when building such processing pipelines, the input and output types of the functors
* bound into the pipeline need to be compatible or convertible. The transformation
* functor supports the same definition styles as described for #expand
* - it can be pure functional, src -> res
* - it can accept the underlying source iterator and exploit side-effects
*/
template<class FUN>
auto
transform (FUN&& transformFunctor)
{
using ResCore = iter_explorer::Transformer<SRC, FUN>;
using ResIter = typename _DecoratorTraits<ResCore>::SrcIter;
return TreeExplorer<ResIter> (ResCore {move(*this), forward<FUN>(transformFunctor)});
}
/** adapt this TreeExplorer to filter results, by invoking the given functor to approve them.
* The previously created source layers will be "pulled" to fast-forward immediately to the
* next element confirmed this way by the bound functor. If none of the source elements
* is acceptable, the iterator will transition to exhausted state immediately.
*/
template<class FUN>
auto
filter (FUN&& filterPredicate)
{
using ResCore = iter_explorer::Filter<SRC, FUN>;
using ResIter = typename _DecoratorTraits<ResCore>::SrcIter;
return TreeExplorer<ResIter> (ResCore {move(*this), forward<FUN>(filterPredicate)});
}
/** @todo package as IterSource
*/
IterExploreSource<value_type>
asIterSource()
{
UNIMPLEMENTED ("wrap into IterSource");
}
private:
};
/* ==== convenient builder free functions ==== */
/** start building a TreeExplorer
* by suitably wrapping the given iterable source.
* @return a TreeEplorer, which is an Iterator to yield all the source elements,
* but may also be used to build an processing pipeline.
*/
template<class IT>
inline auto
treeExplore (IT&& srcSeq)
{
using SrcIter = typename _DecoratorTraits<IT>::SrcIter;
using Base = iter_explorer::BaseAdapter<SrcIter>;
return TreeExplorer<Base> (std::forward<IT> (srcSeq));
}
} // namespace lib
#endif /* LIB_ITER_TREE_EXPLORER_H */