From 0b487735c2141dd78c491f49823c702a11bd80c4 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 22 Nov 2024 23:54:57 +0100 Subject: [PATCH] Library: extend implementation to support references With this minor change, the internal result-tuple may now also hold references, in case a source iterator exposes a reference (which is in fact the standard case). Under the right circumstances, source-manipulation through the iterator becomes possible. Moreover, the optimiser should now be able to elide the result-value tuple in many cases. and access the iterator internals directly instead. Obviously this is an advanced and possibly dangerous feature, and only possible when no additional transformer functions are interspersed; moreover this prompted a review of some long standing type definitions to more precisely reflect the intention. Note: most deliberately, the Transformer element in IterExplorer must expose a reference type, and capture the results into an internal ItemWrapper. This is the only way we can support arbitrary functions. --- src/lib/iter-adapter-stl.hpp | 12 +- src/lib/iter-adapter.hpp | 42 ++--- src/lib/iter-explorer.hpp | 20 +-- src/lib/iter-zip.hpp | 8 +- src/lib/itertools.hpp | 6 +- src/lib/util-coll.hpp | 20 +-- tests/library/iter-explorer-test.cpp | 4 +- tests/library/iter-zip-test.cpp | 87 +++++++++- wiki/thinkPad.ichthyo.mm | 243 ++++++++++++++++++++++----- 9 files changed, 334 insertions(+), 108 deletions(-) diff --git a/src/lib/iter-adapter-stl.hpp b/src/lib/iter-adapter-stl.hpp index bf595be66..7de5a464f 100644 --- a/src/lib/iter-adapter-stl.hpp +++ b/src/lib/iter-adapter-stl.hpp @@ -429,10 +429,10 @@ namespace iter_stl { * this might or might not yield side-effects. */ template - IterSnapshot (IT& src) + IterSnapshot (IT&& src) { for ( ; src; ++src) - buffer_.push_back(*src); + buffer_.emplace_back (*src); } /** build snapshot from a copy of the Lumiera Iterator @@ -443,15 +443,15 @@ namespace iter_stl { IterSnapshot (IT const& src) { for (IT copy{src}; copy; ++copy) - buffer_.push_back(*copy); + buffer_.emplace_back (*copy); } /** take snapshot by consuming a STL iterator */ template - IterSnapshot (IT& pos, IT const& end) + IterSnapshot (IT&& pos, IT const& end) { for ( ; pos!=end; ++pos) - buffer_.push_back(*pos); + buffer_.emplace_back (*pos); } /** take snapshot from STL iterator */ @@ -459,7 +459,7 @@ namespace iter_stl { IterSnapshot (IT const& begin, IT const& end) { for (IT pos{begin}; pos!=end; ++pos) - buffer_.push_back(*pos); + buffer_.emplace_back (*pos); } IterSnapshot(IterSnapshot &&) = default; diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index 2da826564..54670e223 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -136,6 +136,13 @@ namespace lib { } + namespace iter { + /** type binding helper: an iterato's actual result type */ + template + using Yield = decltype(std::declval().operator*()); + } + + /** * Adapter for building an implementation of the »Lumiera Forward Iterator« concept. @@ -869,8 +876,11 @@ namespace lib { * This allows to build pipelines based on all * numbers "for `i` from `1...N`". This range is _half open_, * i.e. the start is inclusive and the end point is exclusive. - * @remarks basically this is `boost::irange` without any boost `#include` + * @remark default constructed iters are empty and compare equal with + * any other exhausted NumIter; essential requirement for a + * Lumiera Forward Iterator (allows use in range-for-loops). * @tparam INT a number like type, which can be incremented and compared. + * @note deliberately yields by-value and has no `operator->` */ template class NumIter @@ -879,9 +889,9 @@ namespace lib { INT e_; public: - typedef const INT* pointer; - typedef const INT& reference; - typedef INT value_type; + typedef INT* pointer; + typedef INT& reference; + typedef INT value_type; NumIter (INT start, INT end) : i_(start) @@ -911,20 +921,13 @@ namespace lib { /* === lumiera forward iterator concept === */ - reference + value_type operator*() const { _maybe_throw(); return i_; } - pointer - operator->() const - { - _maybe_throw(); - return &i_; - } - NumIter& operator++() { @@ -947,13 +950,18 @@ namespace lib { /** access wrapped index elements */ - const INT& getPos() const { return i_; } - const INT& getEnd() const { return e_; } + INT& getPos() const { return i_; } + INT& getEnd() const { return e_; } ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (NumIter); + /// Supporting equality comparisons... + bool operator!= (NumIter const& o) const { return not operator==(o); } + bool operator== (NumIter const& o) const { return (empty() and o.empty()) // empty iters must be equal (Lumiera iter requirement) + or (i_ == o.i_ and e_ == o.e_); } + private: void _maybe_throw() const @@ -965,12 +973,6 @@ namespace lib { - /// Supporting equality comparisons... - template - inline bool operator== (NumIter const& il, NumIter const& ir) { return (!il && !ir) || (il.getPos() == ir.getPos()); } - - template - inline bool operator!= (NumIter const& il, NumIter const& ir) { return !(il == ir); } diff --git a/src/lib/iter-explorer.hpp b/src/lib/iter-explorer.hpp index 73bb2b539..67052c245 100644 --- a/src/lib/iter-explorer.hpp +++ b/src/lib/iter-explorer.hpp @@ -258,16 +258,9 @@ namespace lib { { }; - /** the _value type_ yielded by a »state core« */ + /** the _result type_ yielded by a »state core« */ template - struct CoreYield - { - using Res = remove_reference_t().yield())>; - - using value_type = typename meta::RefTraits::Value; - using reference = typename meta::RefTraits::Reference; - using pointer = typename meta::RefTraits::Pointer; - }; + using CoreYield = decltype(std::declval().yield()); /** decide how to adapt and embed the source sequence into the resulting IterExplorer */ @@ -281,7 +274,7 @@ namespace lib { struct _DecoratorTraits>> { using SrcRaw = typename lib::meta::Strip::Type; - using SrcVal = typename CoreYield::value_type; + using SrcVal = typename meta::RefTraits>::Value; using SrcIter = lib::IterableDecorator>; }; @@ -434,7 +427,7 @@ namespace lib { /** adapt to a functor, which accepts the value type of the source sequence ("monadic" usage pattern) */ template - struct ArgAdapter + struct ArgAdapter, Arg> ,__not_>>>> // need to exclude the latter, since IterableDecorator { // often seems to accept IT::value_type (while in fact it doesn't) static auto @@ -765,6 +758,11 @@ 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 + * 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 + * producing a value; it provides a safe location for this value to persist. */ template class Transformer diff --git a/src/lib/iter-zip.hpp b/src/lib/iter-zip.hpp index 5e46b5398..7291c0fe1 100644 --- a/src/lib/iter-zip.hpp +++ b/src/lib/iter-zip.hpp @@ -44,7 +44,7 @@ namespace lib { auto buildIterTuple (ITS&& ...iters) { - return make_tuple (lib::explore (std::forward (iters)) ...); + return std::make_tuple (lib::explore (std::forward (iters)) ...); } template @@ -95,14 +95,12 @@ namespace lib { - /** */ - /** * Build a tuple-combining iterator builder * @param iters an arbitrary sequence of _iterable entities_ * @return an IterExplorer to yield result tuples on iteration - * @remark IterExplorer is both a »Lumiera Forward Itertor« and a _Pipeline Builder_ + * @remark IterExplorer is both a »Lumiera Forward Iterator« and a _Pipeline Builder_ * - as Lumiera iterator, it can be used directly in _for-each_ and _while_ loops * - result components can be picked up conveniently through _structural bindings_ for tuples * - using the builder API, results can be postprocessed (apply a function), filtered, searched, reduced... @@ -111,7 +109,7 @@ namespace lib { inline auto zip (ITS&& ...iters) { - auto access_result = [](auto& it){ return *it; }; + auto access_result = [](auto& it)->decltype(auto){ return *it; }; // Note: pass-through result type (maybe reference) auto tuple_results = [&](auto& it){ return meta::mapEach (*it, access_result); }; // auto core = iter::ProductCore{iter::buildIterTuple (std::forward (iters)...)}; diff --git a/src/lib/itertools.hpp b/src/lib/itertools.hpp index 32a10f989..3271117ed 100644 --- a/src/lib/itertools.hpp +++ b/src/lib/itertools.hpp @@ -280,7 +280,7 @@ namespace lib { : IdentityCore { using Raw = IdentityCore; - using Val = typename IT::reference; + using Val = iter::Yield; function predicate_; @@ -678,8 +678,8 @@ namespace lib { template class TransformingCore { - typedef typename IT::reference InType; - typedef wrapper::ItemWrapper Item; + using InType = iter::Yield; + using Item = wrapper::ItemWrapper; function trafo_; diff --git a/src/lib/util-coll.hpp b/src/lib/util-coll.hpp index 541bd008c..d17cea7da 100644 --- a/src/lib/util-coll.hpp +++ b/src/lib/util-coll.hpp @@ -97,9 +97,8 @@ namespace util { * @note the container is taken by \c const& and * the \c const is \em stripped before access. */ - template - inline enable_if< treat_as_STL_Container, - typename COLL::reference > + template >> + inline auto first (COLL const& coll) { using lib::meta::unwrap; @@ -113,9 +112,8 @@ namespace util { * @note the container is taken by \c const& and * the \c const is \em stripped before access. */ - template - inline enable_if< can_direct_access_Last, - typename COLL::reference > + template >> + inline auto last (COLL const& coll) { using lib::meta::unwrap; @@ -129,9 +127,8 @@ namespace util { /** extract the first element yielded by an Lumiera Forward Iterator. * @warning the iterator is modified. */ - template - inline enable_if< treat_as_LumieraIterator, - typename IT::reference > + template >> + inline auto first (IT ii) { __ensure_nonempty(ii); @@ -145,9 +142,8 @@ namespace util { * @note returning by-value, contrary to the other tools in this suite * @note only available when including itertools.hpp beforehand */ - template - inline enable_if< treat_as_LumieraIterator, - typename IT::value_type > + template >> + inline auto last (IT ii) { __ensure_nonempty(ii); diff --git a/tests/library/iter-explorer-test.cpp b/tests/library/iter-explorer-test.cpp index 18444728f..9f9d4ebfd 100644 --- a/tests/library/iter-explorer-test.cpp +++ b/tests/library/iter-explorer-test.cpp @@ -312,9 +312,9 @@ namespace test{ VERIFY_ERROR (ITER_EXHAUST, ++ii ); ii = explore (CountDown{5}); - CHECK (materialise(ii) == "5-4-3-2-1"); + CHECK (materialise(ii) == "5-4-3-2-1"_expect); ii = explore (CountDown{7,4}); - CHECK (materialise(ii) == "7-6-5"); + CHECK (materialise(ii) == "7-6-5"_expect); ii = explore (CountDown{}); CHECK ( isnil (ii)); CHECK (!ii); diff --git a/tests/library/iter-zip-test.cpp b/tests/library/iter-zip-test.cpp index 5def68269..d3e55db95 100644 --- a/tests/library/iter-zip-test.cpp +++ b/tests/library/iter-zip-test.cpp @@ -19,6 +19,7 @@ #include "lib/test/run.hpp" #include "lib/iter-zip.hpp" +#include "lib/iter-explorer.hpp" #include "lib/test/test-helper.hpp" #include "lib/test/diagnostic-output.hpp"/////////////TODO #include "lib/format-util.hpp" @@ -33,7 +34,8 @@ namespace test{ // using ::Test; // using util::isnil; -// using LERR_(ITER_EXHAUST); + using util::join; + using LERR_(ITER_EXHAUST); using lib::meta::forEach; using lib::meta::mapEach; using std::make_tuple; @@ -89,6 +91,9 @@ namespace test{ test_Fixture(); demo_mapToTuple(); demo_construction(); + + verify_iteration(); + verify_references(); UNIMPLEMENTED ("nebbich."); } @@ -100,17 +105,21 @@ namespace test{ auto a = std::array{1u,2u,3u}; auto v = std::vector{{2l,3l}}; - for (auto [u,f] : zip(a,v)) - CHECK (u + 1 == f); + // loop over both in lockstep + for (auto [u,l] : zip(a,v)) + CHECK (u + 1 == l); + // iterate-with index auto it = izip(v); CHECK (it); -SHOW_EXPR(*it) + CHECK (*it == "«tuple»──(0,2)"_expect ); ++it; -SHOW_EXPR(*it) + CHECK (*it == "«tuple»──(1,3)"_expect ); CHECK (it); ++it; CHECK (not it); + VERIFY_ERROR (ITER_EXHAUST, *it ); + VERIFY_ERROR (ITER_EXHAUST, ++it ); } /** @test demonstrate how the test Fixture is used */ @@ -272,12 +281,78 @@ SHOW_EXPR(*it) "«tuple»──(1,2)-" "«tuple»──(2,1)"_expect); } + + + + + /** @test create various product (tuple) iterators + * from mixed source iterators and verify basic iteration. + */ + void + verify_iteration() + { + CHECK (materialise ( + zip (num31(), num32(), num33()) + ) + == "«tuple»──(1,2,3)-" + "«tuple»──(4,5,6)-" + "«tuple»──(7,8,9)-" + "«tuple»──(10,11,12)-" + "«tuple»──(13,14,15)"_expect); + + CHECK (materialise( + izip (num31(), num32(), num33()) + ) + == "«tuple»──(0,1,2,3)-" + "«tuple»──(1,4,5,6)-" + "«tuple»──(2,7,8,9)-" + "«tuple»──(3,10,11,12)-" + "«tuple»──(4,13,14,15)"_expect); + } + + + /** @test verify pass-through of references */ + void + verify_references() + { + auto vec = std::vector{1,5}; + auto arr = std::array{2,3}; + + // Case-1 + auto i1 = izip (vec,arr); + + CHECK (*i1 == "«tuple»──(0,1,2)"_expect ); // initial state points to the first elements, prefixed with index≡0 + get<1>(*i1) = 5; // manipulate through the exposed reference + CHECK (*i1 == "«tuple»──(0,5,2)"_expect ); // effect of manipulation is visible + + CHECK (join(vec) == "5, 5"_expect ); // manipulation indeed flipped the first element in the vector + CHECK (join(arr) == "2, 3"_expect ); // (while the array remains unaffected) + + // Case-1 + auto i2 = izip (explore(vec).transform([](uint v){ return v-1; }) // this time the first iterator is a pipeline with a transformer + ,arr); // while the second one is again a direct iteration of the array + + CHECK (*i2 == "«tuple»──(0,4,2)"_expect ); // again can see the first elements, and the effect of the transformer + get<0>(*i2) = 9; // manipulate complete result tuple + get<1>(*i2) = 9; + get<2>(*i2) = 9; + CHECK (*i2 == "«tuple»──(9,9,9)"_expect ); // effect of the manipulation is visible + + ++i2; // ...but iteration re-uses the internal result-tuple storage + CHECK (*i2 == "«tuple»──(1,4,3)"_expect ); // and so the effect of the manipulation seems gone + CHECK (join(vec) == "5, 5"_expect ); // ...which is in fact true for the vector, due to the transformer + CHECK (join(arr) == "9, 3"_expect ); // ...while the array could be reached through the reference + } /* SHOW_EXPR (materialise ( - num32().transform(hexed) + zip (num31(), num32(), num33()) ) ) + CHECK (materialise ( + zip (num31(), num32(), num33()) + ) + == ""_expect); */ }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index fbd688681..68e9dbcc1 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -18936,9 +18936,7 @@ - - - +

Hier kein Test mehr notwendig; mehr als alles ausblenden können wir nicht @@ -19177,9 +19175,7 @@ - - - +

setName(cuString&) implementieren @@ -19637,9 +19633,7 @@ - - - +

und das wäre der eigentliche Fall für einen Content-Renderer @@ -20160,9 +20154,7 @@ - - - +

und schaltet den Frame wieder zurück auf ein Text-Label, das natürlich dann in keinster Weise der size-Constraint-Kontrolle unterliegt @@ -21045,9 +21037,7 @@ - - - +

man muß nicht eine explizite Spezialisierung des ganzen Interfaces schreiben @@ -22626,9 +22616,7 @@ - - - +

nach der Mutation erfolgt Display-Neubewertung @@ -25793,9 +25781,7 @@ - - - +

  • @@ -38512,9 +38498,7 @@ - - - +

    die Lösung auf X-Display / GDK-Ebene für die Maus und das grab-widget von GTK, welches alle Events fangen kann. @@ -40923,9 +40907,7 @@ - - - +

    so ±15 2er-Potenz-Schritte genügen, um von der maximalen Auflösung in den Minuten-Bereich zu kommen @@ -42303,9 +42285,7 @@ - - - +

    also davor eine neue Funktion: conformWindowToMetricLimits(px) @@ -43199,9 +43179,7 @@ - - - +

    Die Newton-Approximation kam ja nur ins Spiel, weil ich mit Integer rechnen wollte, in dieser Domain aber die Rechnung nicht umkehren konnte. Im weiteren Kontext betrachtet war das von Anfang an ein Schmuh (und ich habe jetzt schon zwei Tage lang ein dumpfes Gefühl, daß es ein Schmuh ist....) @@ -43803,9 +43781,7 @@ - - - +

    Die Vorlage auf Stackoverflow initialisiert auf 0 oder -1, läuft dann aber für Input==0 leer durch die Checks durch. Meine Variante testet auf 0 und returnt sofort. @@ -44180,9 +44156,7 @@ - - - +

    das ist essentiell wichtig. "Negative" Zeiten dürfen sich keinesfalls  anders verhalten. Eine andere Quantsierungs-Regel kann man dann ggfs. high-level auf ein left-Truncate aufsetzen (z.B. Mitte Frame-Intervall) @@ -45625,9 +45599,7 @@ - - - +

    aber: zufällige ID macht Objekt-builder stateful @@ -52948,7 +52920,7 @@ - + @@ -52957,6 +52929,183 @@ + + + + + + + + + + + + + + +

    + NumIter hatte ich bei Gelegenheit aus Test-Code extrahiert, ohne weiter viel Gedanken darauf zu ver(sch)wenden.... +

    + +
    +
    + + + + +

    + Bestenfalls eliminiert der Optimiser ohnehin alle Indirektionen, aber auch alle Kopien. Wenn das aber nicht geht, weil es von irgendwo noch einen Zugriff geben könnte, dann sitzt man auf einem Speicherzugriff, wo andernfalls direkt ein Register verwendet werden könnte. Und wenn dann auch noch die Referenz non-const ist, versaut man sich leicht internen State und wundert sich dann... Nee, nee! Wenn man wirklich Seiteneffekte über Referenzen möchte, dann soll man sich das bitte explizit auscoden, aber nicht eine Library-Funktion kreativ nutzen +

    + + +
    +
    + + + + + + +

    + ich kann mir keinen Anwendungsfall vorstellen, wo so etwas nicht zumindest überraschend wäre.... Ja man könnte INT = custom magic type setzen, aber wie war das nochmal mit Magie und so? +

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

    + Das heißt: in dem Moment wo wir die transformer-Funktion tatsächlich installieren, packen wir sie in ein std::function-Objekt mit einer neu synthetisierten Signatur. Diese ist (der Intention nach) paßgenau, d.h. sie nimmt den yield des vorgelagerten Iterators als Input +

    + + +
    +
    + + + + + + +

    + typedef typename IT::reference InType; +

    +

    + function<VAL(InType)> trafo_; +

    + + +
    +
    + + + + +

    + damals wollte ich erst mal schnell durch, wie üblich; +

    +

    + viele Jahre später habe ich dann den NumIter definiert, und das ist vielleicht das erste Beispiel, bei dem dieses neue Konzept wirklich ausgereitzt wurde. Ich habe so dunkel in Erinnerung, daß es dann nicht funktioniert hat, und ich deshalb „einfach“ das const in die eingebetteten Ergebnis-Typdefinitionen gepackt habe. Was ja nicht falsch war, aber der Weg des geringsten Wiederstandes +

    + + +
    +
    + + + + + +

    + Und das ist mir zunehmend wichtiger geworden, vor allem nach meinen Erfahrungen mit dem monadischen IterExplorer v1. Grade weil man manchmal wirklich nur Values bieten kann, muß diese Hintertür offen bleiben. Andererseits wäre es ein gefährlicher Fehlschluß, daß eine Type-def »reference« hier „in Wirkklichkeit“ meint, das was der Iterator liefert. Der Schluß liegt nahe, und deshalb bin ich ihm hier auch verfallen. Aber wenn man mit derartigem Konzept-Mapping erst mal anfängt, dann verliert man schnell die Kontrolle +

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

    + in iter-adapter.hpp +

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

    + also exakt die zentrale Stelle, an der entschieden wird, ob eine nachgeschaltete Funktion auf den Resultat-Wert losgeht, oder direkt auf den Iterator-Typ zugreift +

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

    + jetzt wirds aber brandgefährlich +

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

    + ...das würde bedeuten: man reicht eine weitere Typ-Def komplett durch die ganze Kette durch, und jeweils nur am Ende der Kette würde diese für den operator* gelten. Da wir aber Ketten im Builder auch noch nachträglich weiter verlängern, indem eine neue Core darüber gesetzt wird, ist das jedoch nicht ohne Weiteres realisierbar +

    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    @@ -52968,6 +53117,14 @@ + + + + + + + +