diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index 70386552c..1c70f6363 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -637,13 +637,15 @@ namespace lib { * checks; it might be a good idea to build safety checks into the Core * API functions instead, or to wrap the Core into \ref CheckedCore. * @tparam T nominal result type (maybe const, but without reference). - * The resulting iterator will yield a reference to this type T * @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. * 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 of type `T&` + * @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 and also + * #operator-> is then disabled, to prevent taking the adress of the value (temporary) * @see IterExplorer a pipeline builder framework on top of IterableDecorator * @see iter-explorer-test.hpp * @see iter-adaptor-test.cpp @@ -664,9 +666,17 @@ namespace lib { } public: - typedef T* pointer; - typedef T& reference; - typedef T value_type; +// typedef T* pointer; +// typedef T& reference; +// typedef T value_type; + /////////////////////////////////////////////////////////////////////////////OOO new YieldRes code + using CoreYield = decltype(std::declval().yield()); + using _CommonT = meta::CommonResultYield; + using YieldRes = typename _CommonT::ResType; + using value_type = typename _CommonT::value_type; + using reference = typename _CommonT::reference; + using pointer = typename _CommonT::pointer; + /** by default, pass anything down for initialisation of the core. * @note especially this allows move-initialisation from an existing core. @@ -689,22 +699,30 @@ namespace lib { explicit operator bool() const { return isValid(); } - reference + YieldRes +// reference operator*() const { - return _core().yield(); // core interface: yield + return _core().yield(); // core interface: yield } +// lib::meta::enable_if_c<_CommonT::isRef, +// pointer > pointer operator->() const { - return & _core().yield(); // core interface: yield + if constexpr (_CommonT::isRef) + return & _core().yield(); // core interface: yield + else + static_assert (_CommonT::isRef, + "can not provide operator-> " + "since iterator pipeline generates a value"); } IterableDecorator& operator++() { - _core().iterNext(); // core interface: iterNext + _core().iterNext(); // core interface: iterNext return *this; } diff --git a/src/lib/iter-explorer.hpp b/src/lib/iter-explorer.hpp index 0e4429e90..f56636aac 100644 --- a/src/lib/iter-explorer.hpp +++ b/src/lib/iter-explorer.hpp @@ -320,30 +320,29 @@ namespace lib { * helper to derive a suitable common type when expanding children * @tparam SRC source iterator fed into the Expander * @tparam RES result type of the expansion function + * @note this also implies the decision, if the common result + * can be exposed by-ref or must be delivered as value, + * which may have further ramification down the pipeline. */ template struct _ExpanderTraits { using ResIter = typename _DecoratorTraits::SrcIter; - using SrcYield = typename ValueTypeBinding::value_type; - using ResYield = typename ValueTypeBinding::value_type; - static constexpr bool can_reconcile = - has_TypeResult>(); + using SrcYield = iter::Yield; + using ResYield = iter::Yield; + using _CommonT = meta::CommonResultYield; + static constexpr bool can_reconcile = _CommonT::value; + static constexpr bool isRefResult = _CommonT::isRef; static_assert (can_reconcile, "source iterator and result from the expansion must yield compatible values"); static_assert (is_const_v == is_const_v, "source and expanded types differ in const-ness"); - // NOTE: unfortunately std::common_type decays (strips cv and reference) - // in C++20, there would be std::common_reference; for now we have to work around that - using CommonType = conditional_t or is_const_v - , const common_type_t - , common_type_t - >; - using value_type = typename ValueTypeBinding::value_type; - using reference = typename ValueTypeBinding::reference; - using pointer = typename ValueTypeBinding::pointer; + using YieldRes = typename _CommonT::ResType; + using value_type = typename _CommonT::value_type; + using reference = typename _CommonT::reference; + using pointer = typename _CommonT::pointer; }; }//(End) IterExplorer traits @@ -545,6 +544,14 @@ namespace lib { * need not be implemented in the same way, which simplifies the definition of algorithms. * @tparam SRC the wrapped source iterator, typically a IterExplorer or nested decorator. * @tparam FUN the concrete type of the functor passed. Will be dissected to find the signature + * @note the _return type_ of #yield depends _both_ on the return type produced from the original + * sequence and the return type of the sequence established through the expand functor. + * An attempt is made to _reconcile these_ and this attempt may fail (at compile time). + * The reason is, any further processing downstream can not tell if data was produced + * by the original sequence of the expansion sequence. Notably, if one of these two + * delivers results by-value, then the Expander will _always_ deliver all results + * by-value, because it would not be possible to expose a reference to some value + * that was just delivered temporarily from a source iterator. */ template class Expander @@ -617,9 +624,10 @@ namespace lib { public: /* === Iteration control API for IterableDecorator === */ /** @note result type bindings based on a common type of source and expanded result */ + using YieldRes = typename _Trait::YieldRes; using value_type = typename _Trait::value_type; - using reference = typename _Trait::reference; - using pointer = typename _Trait::pointer; + using reference = typename _Trait::reference; + using pointer = typename _Trait::pointer; bool @@ -631,7 +639,7 @@ namespace lib { or SRC::isValid(); } - reference + YieldRes yield() const { return hasChildren()? **expansions_ diff --git a/src/lib/meta/trait.hpp b/src/lib/meta/trait.hpp index c9013a6a3..733cfdaf6 100644 --- a/src/lib/meta/trait.hpp +++ b/src/lib/meta/trait.hpp @@ -107,6 +107,15 @@ namespace meta { using std::__and_; using std::__or_; + template + static constexpr bool isConst_v = std::is_const_v>; + template + static constexpr bool isLRef_v = std::is_lvalue_reference_v; + template + static constexpr bool isRRef_v = std::is_rvalue_reference_v; + template + static constexpr bool isRef_v = std::is_reference_v; + /** * Helper for type analysis and convenience accessors: diff --git a/src/lib/meta/value-type-binding.hpp b/src/lib/meta/value-type-binding.hpp index 4d1c7dd6d..8289f1ef4 100644 --- a/src/lib/meta/value-type-binding.hpp +++ b/src/lib/meta/value-type-binding.hpp @@ -119,6 +119,43 @@ namespace meta { }; + /** + * Decision helper to select between returning results by value or reference. + * - when both types can not be reconciled, not type result is provided + * - when one of both types is `const`, the `ResType` will be const + * - when both types are LValue-references, then the result will be a reference, + * otherwise the result will be a value type + */ + template>()> + struct CommonResultYield + : std::false_type + { }; + + template + struct CommonResultYield + : std::true_type + { + using _Common = std::common_type_t; + // NOTE: unfortunately std::common_type decays (strips cv and reference) + static constexpr bool isConst = isConst_v or isConst_v; + static constexpr bool isRef = isLRef_v and isLRef_v; + + using _ConstT = std::conditional_t; + using _ValRef = std::conditional_t + , std::remove_reference_t<_ConstT> + >; + + using ResType = _ValRef; + using value_type = typename RefTraits::Value; + using reference = typename RefTraits::Reference; + using pointer = typename RefTraits::Pointer; + }; + + }} // namespace lib::meta #endif /*LIB_META_VALUE_TYPE_BINDING_H*/ diff --git a/tests/12metaprogramming.tests b/tests/12metaprogramming.tests index dcb1d0da9..d991f6d97 100644 --- a/tests/12metaprogramming.tests +++ b/tests/12metaprogramming.tests @@ -312,6 +312,11 @@ return: 0 END +TEST "Lumiera state-core iteration" IterCoreAdapter_test 20 < + +  **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. + +* *****************************************************************/ + +/** @file iter-core-adapter-test.cpp + ** unit test \ref IterCoreAdapter_test + */ + + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/util.hpp" +#include "lib/test/diagnostic-output.hpp"////////////TODO + +#include "lib/iter-adapter.hpp" + +//#include +//#include + + + +namespace lib { + using util::unConst; +namespace test{ + + using LERR_(ITER_EXHAUST); +// using boost::lexical_cast; +// using util::for_each; +// using util::isnil; + using util::isSameObject; +// using std::vector; + + + namespace { + + /** + * A »*State Core*« to step down numbers to zero. + * @note this is a minimal description of a state progression towards a goal + * - default constructed is equivalent to _goal was reached_ + * - can be copied, manipulated and compared + * - yields reference to internal state + * - no safety checks whatsoever + */ + struct StepDown + { + uint n; + + StepDown(uint start =0) + : n(start) + { } + + bool + checkPoint () const + { + return n != 0; + } + + uint& + yield () const + { + return util::unConst(n); + } + + void + iterNext () + { + --n; + } + + bool + operator== (StepDown const& o) const + { + return n == o.n; + } + }; + } // (END) impl test dummy container + + + + + + + + /*****************************************************************//** + * @test cover the concept of a »state core«, which is used in Lumiera + * for various aspects of data generation and iteration. + * @see IterStateWrapper + * @see iter-explorer.hpp + */ + class IterCoreAdapter_test : public Test + { + virtual void + run (Arg) + { + simpleUsage(); + stateManipulation(); + checked_and_protected(); + value_and_reference_yield(); + } + + + + + + /** @test build a »Lumiera Forward Iterator« + * to transition a State-Core towards it final state + */ + void + simpleUsage() + { + auto it = IterableDecorator{3}; + CHECK (it); + CHECK (*it == 3); + ++it; + CHECK (it); + CHECK (*it == 2); + ++it; + CHECK (it); + CHECK (*it == 1); + ++it; + CHECK (not it); + } + + + /** @test state of a decorated un-checked core can be manipulated */ + void + stateManipulation() + { + auto it = IterableDecorator{}; + CHECK (not it); + CHECK (*it == 0); + ++it; + CHECK (*it == uint(-1)); + CHECK (it); + *it = 5; + CHECK (*it == 5); + ++it; + CHECK (*it == 4); + it.n = 1; + CHECK (*it == 1); + CHECK (it); + ++it; + CHECK (not it); + CHECK (it.n == 0); + CHECK (isSameObject(*it, it.n)); + } + + + /** @test additional wrappers to add safety checks + * or to encapsulate the state core altogether */ + void + checked_and_protected() + { + auto cc = CheckedCore{2}; + CHECK (cc.checkPoint()); + CHECK (cc.yield() == 2u); + cc.n = 1; + CHECK (cc.yield() == 1u); + cc.iterNext(); + CHECK (not cc.checkPoint()); + CHECK (cc.n == 0u); + VERIFY_ERROR (ITER_EXHAUST, cc.yield() ); + VERIFY_ERROR (ITER_EXHAUST, cc.iterNext() ); + + auto it = IterStateWrapper{StepDown{2}}; + CHECK (it); + CHECK (*it == 2u); + ++it; + CHECK (*it == 1u); + ++it; + CHECK (not it); + VERIFY_ERROR (ITER_EXHAUST, *it ); + VERIFY_ERROR (ITER_EXHAUST, ++it ); + } + + + + /** @test adapters can (transparently) handle a core which yields values + * - demonstrate how cores can be augmented by decoration + * - the decorated core here yields by-value + * - both CheckedCore and IterableDecorator can cope with that + * - the result is then also delivered by-value from the iterator + * @remark the »Lumiera Forward Iterator« concept does not exactly specify + * what to expect when dereferencing an iterator; yet for obvious reasons, + * most iterators in practice expose a reference to some underlying container + * or internal engine state, since this is more or less the whole point of using + * an iterator: we want to expose something for manipulation, without revealing + * what it actually is (even while in most cases the implementation is visible + * for the compiler, the code using the iterator is not tightly coupled). This + * scheme has ramifications for the way any iterator pipeline works; notably + * any _transformation_ will have to capture a function result. However, + * sometimes an iterator can only return a computed value; such a usage + * can be valid and acceptable and is supported to the degree possible. + */ + void + value_and_reference_yield () + { + struct ValueStep + : StepDown + { + using StepDown::StepDown; + int yield() const { return StepDown::yield(); } + }; + + auto it = IterableDecorator>{2}; + CHECK (it); + CHECK (*it == 2); + CHECK (it.n == 2); + CHECK (not isSameObject(*it, it.n)); + CHECK (showType() == "int"_expect); + + StepDown& ix{it}; + CHECK (ix.yield() == 2u); + CHECK ( isSameObject(ix.yield(), ix.n)); + CHECK ( isSameObject(ix.yield(), it.n)); + CHECK (showType() == "uint&"_expect); + + ++it; + CHECK (*it == 1); + ++it; + VERIFY_ERROR (ITER_EXHAUST, *it ); + VERIFY_ERROR (ITER_EXHAUST, ++it ); + } + + + }; + + LAUNCHER (IterCoreAdapter_test, "unit common"); + + +}} // namespace lib::test + diff --git a/tests/library/iter-zip-test.cpp b/tests/library/iter-zip-test.cpp index 9ecaf78ed..d61e4fd8f 100644 --- a/tests/library/iter-zip-test.cpp +++ b/tests/library/iter-zip-test.cpp @@ -34,6 +34,7 @@ namespace test{ using util::join; using util::isnil; + using util::noneg; using LERR_(ITER_EXHAUST); using lib::meta::forEach; using lib::meta::mapEach; @@ -66,48 +67,6 @@ namespace test{ #define TYPE(_EXPR_) showType() - template - struct _PipelineDetector - { - using RawIter = SRC; - }; - template - struct _PipelineDetector > - { - using RawIter = typename SRC::TAG_Explode; - }; - - struct BOO - { - int uh{42}; - }; - - template - struct Moo - : SRC - { - using TAG_Explode = SRC; - }; - - template - struct D - : SRC - { }; - - void - plonk() - { - using P1 = D>; - using P2 = D>>>; - - using R1 = typename _PipelineDetector::RawIter; - using R2 = typename _PipelineDetector::RawIter; - -SHOW_TYPE(P1) -SHOW_TYPE(R1) -SHOW_TYPE(P2) -SHOW_TYPE(R2) - } /*********************************************************************************//** @@ -126,7 +85,6 @@ SHOW_TYPE(R2) virtual void run (Arg) { - plonk(); simpleUsage(); test_Fixture(); demo_mapToTuple(); @@ -135,6 +93,7 @@ SHOW_TYPE(R2) verify_iteration(); verify_references(); verify_pipelining(); + verify_exploration(); } @@ -485,7 +444,140 @@ SHOW_TYPE(R2) return a+b+c; }) == 6+15+24+33+42); -SHOW_EXPR(TYPE(izip(num31()))) + } + + + template + void + resu() + { MARK_TEST_FUN + using RT = meta::CommonResultYield; +SHOW_EXPR(RT()) + if constexpr (RT()) + { +SHOW_EXPR(RT::isConst) +SHOW_EXPR(RT::isRef) +SHOW_TYPE(typename RT::_Common ) +SHOW_TYPE(typename RT::_ConstT ) +SHOW_TYPE(typename RT::_ValRef ) +SHOW_TYPE(typename RT::ResType ) +SHOW_TYPE(typename RT::reference) + } + } + /** @test verify the interplay of _child expansion_ and tuple-zipping. + * @remark the expansion mechanism implies that a _child sequence_ is generated + * by an _expand functor,_ based on the current iterator value at that point. + * The tricky part here is that this expand functor can sit somewhere in the + * source iterators, while the actual signal to expand is sent from »downstream« + * and has to be propagated to all children. + * Thus two expander-setups are demonstrated first, and then triggered from + * a combined iterator, dispatching the trigger over the tuple-zipping step. + * - the expansion-sequences unfold the same in each case + * - the shortest sequence terminates the overall zip()-evaluation + * - when generating the `expandChildrem()` call _after_ the `zip()`, + * it is also passed to other iterators that have no expand-functor defined; + * for those, it is absorbed without effect. Now, since the expandAll() + * actually works by replacing the iterate() by expandChildern(), this means + * that the _other sequences_ just do not make any progress. + */ + void + verify_exploration() + { +/* + resu(); + resu(); + resu(); + resu(); + resu(); + resu(); + resu(); + resu(); + resu(); +*/ + + CHECK (materialise ( + num31() + ) + == "1-4-7-10-13"_expect); + + CHECK (materialise ( + explore(num31()) + .expand ([](int i){ return NumIter{noneg(i-1),i}; }) + .expandAll() + ) + == "1-0-4-3-2-1-0-7-6-5-4-3-2-1-0-10-9-8-7-6-5-4-3-2-1-0-13-12-11-10-9-8-7-6-5-4-3-2-1-0"_expect); + + CHECK (materialise ( + explore(num31()) + .expand ([](int i){ return NumIter{noneg(i-2),i-1}; }) + .expandAll() + ) + == "1-4-2-0-7-5-3-1-10-8-6-4-2-0-13-11-9-7-5-3-1"_expect); + + CHECK (materialise ( + zip + ( eachNum(10) + , explore(num31()) + .expand ([](int i){ return NumIter{noneg(i-1),i}; }) + .expandAll() + , explore(num31()) + .expand ([](int i){ return NumIter{noneg(i-2),i-1}; }) + .expandAll() + ) + ) + == "«tuple»──(10,1,1)-" + "«tuple»──(11,0,4)-" + "«tuple»──(12,4,2)-" + "«tuple»──(13,3,0)-" + "«tuple»──(14,2,7)-" + "«tuple»──(15,1,5)-" + "«tuple»──(16,0,3)-" + "«tuple»──(17,7,1)-" + "«tuple»──(18,6,10)-" + "«tuple»──(19,5,8)-" + "«tuple»──(20,4,6)-" + "«tuple»──(21,3,4)-" + "«tuple»──(22,2,2)-" + "«tuple»──(23,1,0)-" + "«tuple»──(24,0,13)-" + "«tuple»──(25,10,11)-" + "«tuple»──(26,9,9)-" + "«tuple»──(27,8,7)-" + "«tuple»──(28,7,5)-" + "«tuple»──(29,6,3)-" + "«tuple»──(30,5,1)"_expect); + + CHECK (materialise ( + zip + ( eachNum(10) + , explore(num31()) + .expand ([](int i){ return NumIter{noneg(i-1),i}; }) + , explore(num31()) + .expand ([](int i){ return NumIter{noneg(i-2),i-1}; }) + ) + .expandAll() // ◁──────────┲━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ note the difference: expand triggered after the zip() + ) // ▽ + == "«tuple»──(10,1,1)-" + "«tuple»──(10,0,4)-" + "«tuple»──(10,4,2)-" + "«tuple»──(10,3,0)-" + "«tuple»──(10,2,7)-" + "«tuple»──(10,1,5)-" + "«tuple»──(10,0,3)-" + "«tuple»──(10,7,1)-" + "«tuple»──(10,6,10)-" + "«tuple»──(10,5,8)-" + "«tuple»──(10,4,6)-" + "«tuple»──(10,3,4)-" + "«tuple»──(10,2,2)-" + "«tuple»──(10,1,0)-" + "«tuple»──(10,0,13)-" + "«tuple»──(10,10,11)-" + "«tuple»──(10,9,9)-" + "«tuple»──(10,8,7)-" + "«tuple»──(10,7,5)-" + "«tuple»──(10,6,3)-" + "«tuple»──(10,5,1)"_expect); } /* SHOW_EXPR diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 71519ef76..8825a4b7b 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -19570,9 +19570,7 @@ - - - +

Ein Stichwort, eine (ggfs. dynamische) Kurzinformation, ein pivot-Frame als Erkennungszeichen @@ -19956,9 +19954,7 @@ - - - +

ASSERTION : widget muß bereits realized sein @@ -20417,9 +20413,7 @@ - - - +

...und das heißt, wir betreiben ggfs sogar erheblichen Aufwand, @@ -20870,9 +20864,7 @@ - - - +

...wohl ehr nicht, aber sie sind nicht allgemeingültig, das ist das Problem @@ -21879,9 +21871,7 @@ - - - +

denn auch der Canvas ist ein Gtk::Container und hat eine Liste von Widgets @@ -23279,9 +23269,7 @@ - - - +

Widgets arbeiten stets in Pixeln @@ -26362,9 +26350,7 @@ - - - +

....sonst wird es zur Sackgasse. @@ -37003,9 +36989,7 @@ - - - +

denn es ist gradezu der Sinn von Glib::Dispatcher, @@ -39957,9 +39941,7 @@ - - - +

  • @@ -41221,9 +41203,7 @@ - - - +

    da wird die Rechnung schwierig, denn ich kann es nicht ohne Weiteres mathematisch greifen.... @@ -42270,9 +42250,7 @@ - - - +

    ...zwar ist der Bias in Richtung auf ein größeres Fenster weiterhin im Code gegeben, aber dieser Fall hier zeigt eben, daß er durch andere Rundungs-Effekte sogar übersteuert werden kann; aber wir können sogar eine stärkere Bedinung errichten, nämlich daß wir nicht zu weit von der Vorgabe abweichen @@ -42736,9 +42714,7 @@ - - - +

    Grund sind die expliziten Limitierungen in dieser Funktion @@ -43224,9 +43200,7 @@ - - - +

    1000_r/LIM_HAZARD * 1024_r/1023 @@ -43555,9 +43529,7 @@ - - - +

    ...aber ein Zähler > INT_MAX / Time::SCALE kann unmöglich sinnvoll weiterverarbeitet werden, und außerdem sollte er für eine Metrik gar nicht auftreten können, weil die Metrik ja relativ stark in der Größe beschränkt ist @@ -52753,13 +52725,12 @@ - + - - - + + @@ -52833,21 +52804,23 @@ - - - - - + + + + + + - + + - - + + @@ -52872,8 +52845,7 @@ andere Tricks mit der _rawCore()-Zugriffsfunktion scheinen nicht zu gehen

    - -
    +
    @@ -52904,6 +52876,7 @@ + @@ -53135,6 +53108,7 @@ + @@ -53142,7 +53116,7 @@ - + @@ -53176,47 +53150,62 @@ - - + + - - - - - - + + + + + + + + + - - + + - - + + - + + - - - + + + + +

    + ...auch wenn es normalerweise kein Performance-Problem darstellt, weil der Optimiser redundante Aufrufe problemlos eliminiert, macht es die Typen für den Außenstehend noch viel undurchtringbarer, und belastet auch die Debug-Builds durch den Umfang unnötiger Typ-Information +

    + + +
    + +
    + +
    - + - + - + @@ -53261,8 +53250,7 @@ weil der Filter selber ja nur den Feed pullt, aber selber keine Werte speichert; damit erzeugt er möglicherweise upstream obsolete Referenzen.

    - -
    +
    @@ -53312,8 +53300,7 @@ das Design hat hier eine Schwachstelle

    - - +
    @@ -53324,12 +53311,226 @@
    - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + ...dann würden wir eine ganze Pipeline »flippen« können, wenn der erste Result-Typ auf Value schwenkt +

    + +
    +
    + + + + +

    + habe die mehrfachen Adapter nun allesamt weg; daher wäre es nun doch denkbar, da die Call-Pfade nun von einem Punkt aus aufspalten (nämlich Core::yield ->  entweder operator* oder operator-> ) +

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

    + Booo!  is_const_v<T&> ist immer false +

    + + +
    +
    + + + + + +

    + ruck-zuck ein paar Stunden weg ... hätte man doch gleich einen Test geschrieben +

    + +
    + +
    +
    + + - - - + + + + + + + + +
    + + + + + + + +
      +
    • + dediziertes Trait schaffen +
    • +
    • + Propagations-Logik explizit auscoden +
    • +
    • + den operator-> auf Values weglassen bzw mit compile-Fehler unterdrücken +
    • +
    + + +
    +
    + + + + +

    + ...da bisher alles auf Rückgabe by-ref ausgelegt ist, und dieser Fall deshalb durch die Änderung ohne Modifikation durchgereicht wird +

    + +
    +
    + + + + +

    + es wird vermutlich auf wenige Fälle beschränkt bleiben, in denen es aber durchaus hilfreich ist, da sowohl Generatoren als auch Transformatoren einfacher zu schreiben sind +

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

    + der auto-Expander kann und darf ja nicht wissen, wo genau unterhalb die Expansion stattfindet; wenn eben gar keine stattfindet, dann bleiben alle durchgereichten expandChildren()-Aufrufe ohne Wirkung, aber ein iterNext() wird dann auch nicht mehr gesendet +

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