From 00098dd815809aa597b893ab76a5a97544d8a120 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 27 Mar 2026 23:32:55 +0100 Subject: [PATCH] Library: develop a flattening primitive for IterExplorer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a feature I wasn’t sure for a long time how best to tackle. It’s worth noting that an initial attempt at `IterExplorer` was based on the monad pattern, which turned out to be a mistake, since monads are more of an anti-pattern, than being genuinely useful. The actual use-case that made me reconsider such a feature it to build an seamlessly chained sequence from several iterators. It would be simple to develop a suitable utility as a stand-alone helper, yet it seemed worth the effort to integrate this into the `IterExplorer` ecosystem, due to the flexible automatic adaptation to iterable source pipelines provided therein. Going this route incurred some cost however, unveiling several defects related to handling nested iterable types in the generic processing framework. --- src/lib/iter-adapter.hpp | 15 +- src/lib/iter-explorer.hpp | 116 +++- src/lib/meta/trait.hpp | 6 +- src/lib/meta/value-type-binding.hpp | 6 +- tests/lib/iter-explorer-test.cpp | 42 ++ wiki/thinkPad.ichthyo.mm | 871 +++++++++++++++++++++++++++- 6 files changed, 1023 insertions(+), 33 deletions(-) diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index 804ad17b6..c2705a309 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -198,7 +198,7 @@ namespace lib { using meta::RefTraits; - /** type binding helper: an iterato's actual result type */ + /** type binding helper: an iterator's actual result type */ template using Yield = decltype(std::declval().operator*()); @@ -207,15 +207,14 @@ namespace lib { using CoreYield = decltype(std::declval().yield()); + /** some legacy STL type iterators do not strictly fulfil all requirements, + * yet were marked by a specialisation of `std::iterator_traits` */ template concept legacy_traits_marked = requires { - typename std::iterator_traits::value_type; + typename std::iterator_traits::iterator_category; }; - template - concept stl_iter = std::input_iterator or legacy_traits_marked; - /** Iterator-Traits: definitions to level the difference between * Lumiera-Iterators and the STL usage of ranges / iterators. @@ -224,9 +223,11 @@ namespace lib { struct Trait { static constexpr bool is_LumieraIter = can_IterForEach::value; - static constexpr bool is_STLIter = stl_iter; + static constexpr bool is_STLIter = std::input_iterator; + static constexpr bool is_Legacy = legacy_traits_marked; - static_assert(is_LumieraIter or is_STLIter, "source type must be an iterator"); + static_assert(is_LumieraIter or is_STLIter or is_Legacy + ,"source type must be an iterator (either Lumiera or STL"); using _ValTrait = std::conditional_t , RefTraits>>; diff --git a/src/lib/iter-explorer.hpp b/src/lib/iter-explorer.hpp index 34a864f3e..98b48a049 100644 --- a/src/lib/iter-explorer.hpp +++ b/src/lib/iter-explorer.hpp @@ -13,13 +13,13 @@ */ /** @file iter-explorer.hpp - ** Building tree expanding and backtracking evaluations within hierarchical scopes. + ** Build tree expanding and backtracking evaluations within hierarchical scopes. ** Based on the *Lumiera Forward Iterator* concept and using the basic IterAdapter 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 + ** 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 historically, this template, as well as the initial IterExplorer (draft from 2012) can be @@ -36,7 +36,7 @@ ** 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. (strictly speaking, it means the `flatMap` operation) + ** to _map the function onto_ the elements in the list. (more precisely, this indicates a `flatMap` operation) ** ** # A Pipeline builder ** Based on such concepts, structures and evaluation patterns, the IterExplorer serves the purpose to provide @@ -49,8 +49,9 @@ ** 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 + ** - 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 iterator results + ** - the **flatten** operation allows to combine a sequence of nested _iterables_ into a single seamless iterator ** - 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. ** - further special convenience adaptors and _terminal functions_ are provided. @@ -273,7 +274,7 @@ namespace lib { template struct _DecoratorTraits>> { - using SrcRaw = lib::meta::Strip::Type; + using SrcRaw = lib::meta::Strip::TypeReferred; using SrcVal = meta::RefTraits>::value_type; using SrcIter = lib::IterableDecorator>; }; @@ -699,7 +700,7 @@ namespace lib { /** * @internal extension to the Expander decorator to perform expansion automatically on each iteration step. - * @todo as of 12/2017, this is more like a proof-of concept and can be seen as indication, that there might + * @todo as of 12/2017, this is more like a proof-of concept and can be seen as indication that there might * be several flavours of child expansion. Unfortunately, most of these conceivable extensions would * require a flexibilisation of Expander's internals and thus increase the complexity of the code. * Thus, if we ever encounter the need of anything beyond the basic expansion pattern, we should @@ -761,6 +762,86 @@ namespace lib { }; + /** + * @internal Decorator for IterExplorer to expand one level of nested iterables. + * This mechanism requires that the _result value_ yielded by the underlying iterator + * is itself iterable. Applying this decorator thus has the effect to »flatten« exactly + * one level of nesting. + * @note The result produced by underlying \a SRC _must be a Lumiera iterator_. + * Other kinds of nested iterables can be handled only when adapted accordingly. + */ + template + class Flattener + : public SRC + { + static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); + + using NestedIT = iter::Yield; + static_assert(can_IterForEach::value, "flatten() operation expects nested iterator"); + + public: + using value_type = meta::ValueTypeBinding::value_type; + using reference = meta::ValueTypeBinding::reference; + using pointer = meta::ValueTypeBinding::pointer; + + Flattener() =default; + // inherited default copy operations + + Flattener (SRC&& dataSrc) + : SRC{move (dataSrc)} + { + pullNested(); + } + + + /** refresh state when other layers manipulate the source sequence. + * @note possibly pulls pipeline to re-establish the invariant */ + void + expandChildren() + { + SRC::expandChildren(); + pullNested(); + } + + public: /* === Iteration control API for IterableDecorator === */ + + bool + checkPoint() const + { + ENSURE (not srcIter() or * srcIter()); + return bool(srcIter()); + } + + reference + yield() const + { + return **srcIter(); + } + + void + iterNext() + { + ++ *srcIter(); + pullNested(); + } + + protected: + SRC& + srcIter() const + { + return unConst(*this); + } + + /** @note Invariant: positioned at next nested source element, if any */ + void + pullNested() + { + while (srcIter() and not * srcIter()) + ++srcIter(); + } + }; + + /** * @internal Decorator for IterExplorer to map a transformation function on all results. @@ -768,7 +849,7 @@ namespace lib { * 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. - * @note the result-type of the #yield() function _must be_ `reference`, even when + * @note the result-type of the #yield() function _must be_ a `reference`, even when * the TransformFunctor produces a value; otherwise we can not provide a safe * `operator->` on any iterator downstream. This is also the reason why the * ItemWrapper is necessary, precisely _because we want to support_ functions @@ -1698,8 +1779,23 @@ namespace lib { } + /** adapt this IterExploer to flatten _one level of nested Lumiera Iterators_. + * Obviously this processing can only be applied when the underlying pipeline yields a result + * that can be handled as being iterable; the resulting pipeline will seamlessly produce all + * results from all non-empty nested iterators, in sequence. + * @remark when chained behind a suitable [transformer(fun)](\ref transform()), the processing + * pattern is similar to the `flatMap(fun)` or `M >= fun` operation known from Monad theory. + * @return processing pipeline with attached [flattening](\ref iter_explorer::Flattener) decorator + */ auto - flatten(); + flatten() + { + using ResCore = iter_explorer::Flattener; + using ResIter = _DecoratorTraits::SrcIter; + + return IterExplorer (ResCore{move(*this)}); + } + /** adapt this IterExplorer 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 diff --git a/src/lib/meta/trait.hpp b/src/lib/meta/trait.hpp index ee54f460a..fd1bb1066 100644 --- a/src/lib/meta/trait.hpp +++ b/src/lib/meta/trait.hpp @@ -524,7 +524,7 @@ namespace meta { template class is_StateCore { - using Type = Strip::Type; + using Type = Strip::TypeReferred; META_DETECT_FUNCTION_ARGLESS(checkPoint); META_DETECT_FUNCTION_ARGLESS(iterNext); @@ -545,7 +545,7 @@ namespace meta { template class can_STL_ForEach { - using Type = Strip::Type; + using Type = Strip::TypeReferred; struct is_iterable { @@ -609,7 +609,7 @@ namespace meta { template class can_STL_backIteration { - using Type = Strip::Type; + using Type = Strip::TypeReferred; struct is_backIterable { diff --git a/src/lib/meta/value-type-binding.hpp b/src/lib/meta/value-type-binding.hpp index 04b1d6101..a1a4da703 100644 --- a/src/lib/meta/value-type-binding.hpp +++ b/src/lib/meta/value-type-binding.hpp @@ -27,9 +27,9 @@ ** Within the STL, there is a convention to provide nested typedefs to indicate ** type variations in relation to the basic payload type of the container. We ** follow this convention and support especially the - ** - `value_type` (what is conceived to be "in" the container or iterator) - ** - a simple (LValue) reference to the payload - ** - a pointer at the payload. + ** - `value_type` : how to store what is conceived to be "in" the container or iterator) + ** - a simple (LValue) reference to the payload: how you access some content + ** - a to form pointer at the payload, which is relevant for `operator->` ** ** A custom container should likewise provide such type definitions, and the ** type rebinding helper template defined in this header makes it easy to diff --git a/tests/lib/iter-explorer-test.cpp b/tests/lib/iter-explorer-test.cpp index c65130661..84faa31e4 100644 --- a/tests/lib/iter-explorer-test.cpp +++ b/tests/lib/iter-explorer-test.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +72,7 @@ using lib::iter_stl::eachElm; using LERR_(ITER_EXHAUST); using std::vector; using std::string; +using std::array; namespace lib { namespace test{ @@ -666,6 +668,46 @@ namespace test{ + /** @test chain the results from a sequence of iterators into a single sequence. + * Since this "sequence" of source iterations can itself be treated as iterable, + * this amounts to _»flattening«_ a single level of nesting. When linked into the + * pipeline behind a transforming function, the combined processing pattern is + * similar to the `flatMap(fun)` or `M >= fun` operations known from Monad theory. + */ + void + verify_flattenNestedIterator() + { + using LumieraIter = _DecoratorTraits::SrcIter; + + // STL-iterable with a sequence of Lumiera iterators + auto nested_1 = array{LumieraIter{CountDown{5}}, LumieraIter{CountDown{8,4}}}; + + CHECK (materialise ( + explore(nested_1) + .flatten() + ) + == "5-4-3-2-1-8-7-6-5"_expect); + + + // Investigate the types used for adapting the iterator pipeline.... + + auto baseType = [](auto& src){ using BasePipeline = _DecoratorTraits::SrcIter; + return showType(); + }; + auto nestedIT = [](auto& src){ using BasePipeline = _DecoratorTraits::SrcIter; + using NestedResult = iter::Yield; + return showType(); + }; + + // the base pipeline fed into the Flattener.. + CHECK (baseType(nested_1) == "iter_explorer::StlRange >, 2ul>&>"_expect ); + + // the nested (iterable) result type produced by this base pipeline... + CHECK (nestedIT(nested_1) == "IterableDecorator >&"_expect ); + } + + + /** @test package elements from the source pipeline into fixed-sized groups. * These groups are implemented as std::array and initialised with the values * yielded consecutively from the underlying source pipeline. The main iterator diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0d54d2840..73e6330e0 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -51897,7 +51897,7 @@ - + @@ -52622,7 +52622,7 @@ - + @@ -52914,8 +52914,8 @@ - - + + @@ -53272,13 +53272,107 @@ - + + + + + + + + + +

+ ein Transform-Layer gefolgt von einem Flatten +

+ +
+
+ + + + +

+ Zur Erinnerung: diesem Ansatz war ich zunächst verfallen, habe dann aber erkannt, daß Monaden sehr oft ein Anti-Pattern sein können. In meinem Fall war das Design dadurch von Grund auf verdorben, so daß ich die nun erfolgreiche Variante komplett neu bauen mußte. Das leitende Prinzip ist jetzt ein Pipeline-Builder, und nicht mehr eine funktionale Kapsel. Indem ich nun aber ein einfaches »flatten« nachrüste, wären auch Monaden-artige Verarbeitungsmuster darstellbar, wenn man sich auf eine Teilmenge der Funktionalität beschränkt. Meiner Erfahrung zufolge dürfte das aber seltener auftreten, als die Suggestiv-Kraft der Monaden-Idee vermuten ließe; typischerweise muß man die Nützlichkeit von Monaden mit speziell ausgewählten Beispielen belegen, was eben grade gegen eine Universallität spricht. +

+

+ +

+

+ Der konkrete Anlaß, warum ich jetzt eine »flatten«-Operation einbaue, ist auch etwas anderes: eine Folge von Iteratoren nahtlos verbinden. Man kann  zwar behaupten, das sei der Beleg für eine Monaden-Struktur, aber das verfehlt den Kern der Sache, denn es ist nicht konstituierend für diesen Vorgang, man kann ihn genauso gut als Coroutine deuten. +

+ +
+
+
+ + + + + + +

+ Monaden leben in der Welt der Typen, deshalb kann man damit auch nicht direkt eine Such-Auswertung bauen (nur indirekt, indem ein Funktionsaufruf einen leeren Iterator liefert). Dem entsprechend ist auch das flatMap-Baumuster an den konkreten Ergebnistyp gebunden, man kann auch eine flatMap-Operation nicht generisch rekursiv anwenden, sondern nur, wenn die gegebenen Typen bereits verschachtelt sind. Hier liegt auch der entscheidene Unterschied zu einer pure functional language: dort gibt es keine Unterscheidung zwischen Typ und Wert, und entsprechend kann auch eine Typ-Auswertung dann doch zur laufzeit rekursiv sein und nicht terminieren. Was jedoch ehr ein Zeiten dafür ist, daß diese »reinen« Begriffe an der gegebenen Sache vorbeigehen. +

+ +
+
+ + + + + + + + + + + + + + + +

+ diese zweischichtige Struktur im Quellcode hat sich schrittweise herausgebildet und inzwischen gut bewährt +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - +

Wobei mir nicht klar ist, ob das Problem grundsätzlicher Natur ist. @@ -53482,9 +53576,766 @@ + + + + + + + +

+ konkret: +

+

+ +

+

+ iter_explorer::StlRange<array<IterableDecorator<CheckedCore<CountDown> > +

+

+                               , 3ul +

+

+                               >& +

+

+                        > +

+ +
+
- + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...denn ich habe im ValueTypeBinding die binding-Typen benannt wie in der STL, aber in RefTraits mein eigenes Schema verwendet +

+ +
+
+ + + + +

+ indem man explizit festlegt, daß die Standard-Instanz des Template gewünscht ist +

+ +
+ + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +

+ ...und damit werden iterator_traits unzuverlässig — wenn sie jemals zuverlässig waren.
Denn diese ganze Idee mit den Iteratoren in C++ war von Anfang an eigentlich ein Concept. All die Iterator-Tags und die iterator_traits waren nur Krücken und Workarounds, und außerdem wenig bekannt (und too long didnt read, gilt auch für mich selber!!!). Insofern konnte man sich bei Iteratoren sowiso auf nix verlassen, jenseits der STL. Somit wird die Situation durch die Concepts jetzt deutlich verbessert +

+ +
+ +
+ + + + + + + + +

+ Das war sowiso (partiell) ein Mißverständnis von mir. +

+
    +
  • + was der Iterator liefert ⟹ reference +
  • +
  • + in was ich diesen »Wert« dann speichere ⟹ value_type +
  • +
+ +
+ + + + + +
+
+

+ template<__detail::__dereferenceable _Tp> +

+

+ using iter_reference_t =  decltype(*std::declval<_Tp&>()); +

+

+ +

+
+
+

+ +

+

+ +

+

+ +

+

+ Es ist einfach schön.... +

+ +
+
+
+ + + + + + + +

+ Auch wenn es auf den ersten Blick so aussieht — war es offensichtlich nicht so gemeint: Und zwar fehlt der bool-Check auf das Iterations-Ende; den leistet nämlich erst der im nächsten Schritt darüber gelegte RangeIter +

+

+ +

+

+ Klar, heute würde ich das anders machen: im Grunde ist das eine Transformer-Pipeline, die ich hier mit sehr einfachen Mitteln (noch vor C++11) realisiert habe. Es funktioniert, indem ein STL-Iterator dekoriert wird, und dabei die Zugriffsfunktion jeweils speziell andgepaßt ist. +

+ +
+
+ + + +
+ + + + + +

+ an der Stelle ist es aufgefallen, weil die static-Assertion getriggert hat; nachdem ich die Assertion auskommentiert habe, lief der Build durch ⟹ es muß sich um einen Formalismus handeln, und wahrscheinlich daran liegen, daß das Concept für input_iterator nicht erfüllt wird (denn ein Lumiera-Iterator soll es ja nicht sein) +

+ +
+ +
+ + + + + + + + + + +
+
+

+ /usr/include/c++/14/bits/iterator_concepts.h:633:13: required for the satisfaction of 'weakly_incrementable<_Iter>' [with _Iter = lib::iter_stl::IterSnapshot<long unsigned int>] +

+

+ /usr/include/c++/14/bits/iterator_concepts.h:647:13: required for the satisfaction of 'input_or_output_iterator<_Iter>' [with _Iter = lib::iter_stl::IterSnapshot<long unsigned int>] +

+

+ /usr/include/c++/14/bits/iterator_concepts.h:669:13: required for the satisfaction of 'input_iterator<auto:47>' [with auto:47 = lib::iter_stl::IterSnapshot<long unsigned int>] +

+

+ /usr/include/c++/14/bits/iterator_concepts.h:634:10: in requirements with '_Iter __i' [with _Iter = lib::iter_stl::IterSnapshot<long unsigned int>] +

+

+ /usr/include/c++/14/bits/iterator_concepts.h:637:18: note: nested requirement '__is_signed_integer_like<typename std::__detail::__iter_traits_impl<typename std::remove_cvref<_Tp>::type, std::incrementable_traits<typename std::remove_cvref<_Tp>::type> >::type::difference_type>' is not satisfied +

+

+ 637 | requires __detail::__is_signed_integer_like<iter_difference_t<_Iter>>; +

+

+ | ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +

+

+ /usr/include/c++/14/bits/iterator_concepts.h:639:12: note: the required expression '(__i ++)' is invalid +

+

+ 639 | __i++; +

+

+ +

+
+
+ +
+ +
+ + + + + + +
+
+ + + + + + + + + + + + +

+ es ist m.E. offensichtlich daß man das nicht näher anschauen soll. Dafür sprechen bereits die komplex eingefädelten Hilfs-Traits, und die davor geschalteten Builder-Funktionen. Sogar wenn mal jemand hier was repariert und erweitert, sehe ich keine Gefahr +

+ +
+
+ + + + + + +

+ „Ich hab genau verstanden wie es funktioniert, und kann deshalb gezielt an zwei Stellen manipulieren, und hab dann bereits mein Ergebnis“ +

+ +
+
+ + + + +

+ ...wenn man den IterExplorer (Pipeline-Builder) verwendet, ihn den jeweiligen Container automatisch adaptieren läßt, und dann nur noch ein passendes Transformer-λ darübersetzt. Dadurch würde der Code sehr viel knapper und auch sofort verständlich.... +

+

+ Allerdings +

+
    +
  • + den Header habe ich noch vor C++11 geschrieben (und den Iter-Explorer gab es auch lange Zeit danach noch nicht) +
  • +
  • + die vom IterExplorer generierten Typen sind monströs. Da ist der hier erzeugte RangeIter<Wrapped-irgendwas... vermutlich verständlicher +
  • +
  • + auch ist die Frage, ob der Optimiser so gut mit IterExplorer klarkommt. Das hab ich nämlich noch nie genau überprüft; wohingegen das hier aufgebaute Konstrukt ganz offensichtlich komplett herunteroptimiert werden kann +
  • +
+ +
+
+ + + + +

+ ...man könnte auch sagen, linke Tasche ↷ rechte Tasche. Ich weiß jetzt (weil ich es gard eben gebaut habe), daß der RangeIter mit den Typdefinitionen verzweigt, wenn er einen Lumiera-Iterator sieht. Außerdem finde ich die Assertion (daß der Typ-Parameter ein Iterator sein muß) insgesamt hilfreich. Und drittens »sehe« ich, daß der volle Umfang des STL-Konzepts gar nicht gebraucht wird für einen Lumiera-Iterator. Trotzdem übel, hier ein lügendes Concept zu verwenden +

+ +
+ +
+ + + + + +
+
+ + + + +

+ Umso mehr, nachdem ich mir jetzt etwas die Implementierung der STL-concepts angeschaut habe.... +

+

+ +

+

+ Fazit: die Idee, einen Iterator als »abstrahierten Pointer« zu definieren, ist auf den ersten Blick cool, und hat auch ihren Nutzen. Aber es ist und bleibt schechtes Design, weil man dadruch lediglich Implementierungs-Eigenschaften in eine formale Spezifikaiton mappt. Ich bleibe dabei: die STL-Iteratoren können viel zu viel, um eine gute Abstraktion zu sein. Daher war es angemessen, für Lumiera ein eigenes Konzept aufzubauen, und dafür den größten Teil aller Fähigkeiten und Möglichkeiten der STL-Iteratoren zu opfern +

+ +
+ + +
+
+
+
+ + + + + + + +

+ bisher verlangt der Lumiera-Iterator-Detektor nur eine Typedef value_type  ; +

+

+ das ist zwar korrekt, und genügt i.d.R. zur Erkennung. Allerdings ist value_type  nicht das Wichtigste (wie ich früher meinte), sondern die Typedef reference, denn diese beschreibt, was der Iterator konkret liefert +

+ +
+ + + + + + + + + + + +

+ läßt sich zurückführen auf den Typ Record::scopeIter +

+ +
+
+
+ + + + + + + + + + + + + + +

+ und das ist hier int& *  ⟹ ↯ UNGÜLTIG  ⟹ SFINAE-fail +

+ +
+ +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ so wie jetzt gebaut mit dem Transformer ⟹ Selbstmord ☠ +

+ + +
+ + + +

+ Der Transformer enthält einen ItemWrapper, in den das Ergebnis der Transformation by value abgelegt wird. Wir machen also entweder eine Kopie der ganzen Quell-Pipeline, oder, noch schlimmer, wir moven die Quell-Pipeline dorthin, und dann beim nächsten Pull rufen wir die Zombie-Quell-Pipeline aus dem Transformer auf. +

+ + +
+
+ + + + + +

+ Das Thema »Dekoration« ist zentral gelöst ⟹ Struktur der  _DecoratorTraits  befolgen +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Das bedeutet: +

+
    +
  • + die Quelle wird per Value genommen und in die Pipeline verschoben.... +
  • +
  • + Effektiv verändert sich die Storage nicht; die Wrapper-Typen sind nur eine logische Dekoration ohne zusätzliche Storage +
  • +
+ + +
+
+
+ + + + + + + + + + + + + + + +

+ Ich hatte ihn ja nur gewählt, weil er wie ein billiger Fix  aussah, mit dem man den Haupt-Fall (Lumiera-Iterator) noch etwas erweitern könnte +

+ +
+ +
+ + + + + + + + + + + +

+ Wenn letztlich nur eine Referenz auf die Quelle durchgereicht werden muß, ggfs. mit Cast in einen anderen Typ, dann kann das direkt in einer yield-Operation erfolgen. Konkret ist das etwas trickreicher, da im einen Fall noch der Quell-Typ (virtuell)recastet werden muß; letztlich überdeckt der Adapter dann aber nur noch den operator* — ohne Implementierungsskizze ist nicht klar, ob sich das in einen einzigen Adapter-Typ zusammenfassen läßt +

+ + +
+ + + + + +

+ wird sie dagegen per Value gegeben, ist sie generiert ⟹ wie Container behandeln +

+ +
+
+
+ + + + + +

+ Um das explizit klarzustellen: +

+
    +
  • + wir haben einen top-level Iterationsmechanismus, der mit jedem Step einen Iterierbaren Wert sichtbar macht, und zwar stets per Referenz (Referenz auf einen Container, bzw. auf ein IterSource-Interface) +
  • +
  • + jedesmal wenn dieser top-Level einen Step macht, muß in einem daneben liegenden inline-Buffer ein neuer Handle-Typ erzeugt (und der alte zerstört) werden. +
  • +
  • + während dann aber der flatten()-Mechanismus diesen nested-Iterator traversiert, bleibt dieses Handle an Ort und Stelle liegen +
  • +
+ +
+
+
+
+ + + + + +

+ bedeutet ja, in der Pipeline liegt ein Iterator / eine Quelle, die den Container generiert +

+ + +
+
+ + + + + + + + + + + + + + + + + + +

+ ...denn sonst wäre es kein virtuelles redressing mehr, sondern die Konstruktion eines Wrappers +

+ +
+
+
+ + + + + +

+ ...der keine Operationen und Funktionen anwendet, sondern nur jeden Wert in einen neu-konstruierten Wrapper überführt +

+ + +
+
+ + + + + + + + +

+ ...denn die Analyse hat gezeigt, daß die DecoratorTraits zwar grundsätzlich das Feld richtig strukturieren, aber einige Differenzierungen nicht unterstützen +

+ +
+
+
+
+
+
+ + + + + + + + + + + + +

+ beispielsweise in stringify() ⟶ für welchen »wert«-Typ brauchen wir einen String-Konverter? +

+ +
+
+
+ + + + + + +

+ da hier stets ein anderer Iterator eingewickelt wird (i.d.R. ein STL-Iterator); also brauchen wir die spezielle Logik, die STL-Iteratoren und Pointer korrekt behandelt +

+ + +
+ +
+ + + + + + + +

+ Möglicherweise überflüssig durch die neue Logik, aber so eine explizite Spezialisierung direkt im Scope ist trotzdem vorteilhaft, weil man sie sieht, und die lokal vorhandenen Bedingungen explizit und fest berücksichtigt werden, ohne eine ggfs. fragile allgemeine Typ-Selektor-Logik einzubeziehen. +

+ + +
+ +
+ + + + + + + +

+ schaut sich an, in was für einen Typ man Werte des unterliegenden, zu gruppierenden Iterators speichern kann — so soll das verwendet werden! +

+ +
+ +
+ + + + + + +
+
+ + + +
+ + + + + + + + + + + + +

+ weil die in einer solchen Situation, in der eine Pipeline erneut erweitert wird, i.d.R. redundant ist +

+ +
+
+ + +
+
+
+
+ + @@ -53563,7 +54414,7 @@ - +