From 252c735b7bc4bdcb1d0e41ee0d84100e217f4b82 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 26 Nov 2024 02:56:28 +0100 Subject: [PATCH] Library: solve forwarding of child-expansion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For sake of completeness, since the `IterExplorer` supports building extended search- and evaluation patterns, a tuple-zipping adapter can be expected to handle these extended usages transparently. While the idea is simple, making this actually happen had several ramifications and required to introduce additional flexibility within the adaptor-framework to cope better with those cases were some iterator must return a value, not a ref. In the end, this could be solved with a bit of metaprogramming based on `std::common_type` ...and indeed, this is all quite nasty stuff — in hindsight, my initial intuition to shy away from this topic was spot-on.... --- src/lib/iter-adapter.hpp | 34 ++- src/lib/iter-explorer.hpp | 40 ++- src/lib/meta/trait.hpp | 9 + src/lib/meta/value-type-binding.hpp | 37 +++ tests/12metaprogramming.tests | 5 + tests/library/iter-core-adapter-test.cpp | 242 +++++++++++++++ tests/library/iter-zip-test.cpp | 180 ++++++++--- wiki/thinkPad.ichthyo.mm | 369 +++++++++++++++++------ 8 files changed, 764 insertions(+), 152 deletions(-) create mode 100644 tests/library/iter-core-adapter-test.cpp 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 +

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