diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index 60c9385a5..70164bd23 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -72,8 +72,8 @@ ** be expected from any such iterator. These rules are similar to STL's ** "forward iterator", with the addition of an bool check to detect ** iteration end. The latter is inspired by the \c hasNext() function - ** found in many current languages supporting iterators. In a similar - ** vein (inspired from functional programming), we deliberately don't + ** found in many current languages supporting iterators. However, by + ** inspiration from functional programming, we deliberately do not ** support the various extended iterator concepts from STL and boost ** (random access iterators, output iterators, arithmetics, difference ** between iterators and the like). According to this concept, @@ -90,6 +90,13 @@ ** _dereferenced_ to yield the "current" value. ** - moreover, iterators may be incremented until exhaustion. ** + ** Conceptually, a Lumiera Iterator represents a lazy stream of calculations + ** rather than a target value considered to be »within« a container. And while + ** the result is deliberately _always exposed as a reference,_ to keep the + ** door open for special-case manipulations, for the typical usage it is + ** _discouraged_ to assume anything about the source, beyond the limited + ** access to some transient state as exposed during active iteration. + ** ** @see iter-adapter-test.cpp ** @see itertools.hpp ** @see IterSource (completely opaque iterator) @@ -326,7 +333,7 @@ namespace lib { * right into the iterator. Contrast this to IterAdapter, which refers to a managing * container behind the scenes. Here, all of the state is assumed to live in the * custom type embedded into this iterator, accessed and manipulated through - * a set of free functions, picked up through ADL. + * a dedicated _iteration control API_ exposed as member functions. * * \par Assumptions when building iterators based on IterStateWrapper * There is a custom state representation type ST. diff --git a/src/lib/several-builder.hpp b/src/lib/several-builder.hpp index fa3ba6e55..28e032476 100644 --- a/src/lib/several-builder.hpp +++ b/src/lib/several-builder.hpp @@ -168,7 +168,7 @@ namespace lib { REQUIRE (bucket); using ElmAlloT = typename AlloT::template rebind_traits; auto elmAllo = adaptAllocator(); - E* loc = & bucket->subscript (idx); + E* loc = reinterpret_cast (& bucket->subscript (idx)); ElmAlloT::construct (elmAllo, loc, forward (args)...); ENSURE (loc); return *loc; @@ -185,7 +185,10 @@ namespace lib { using ElmAlloT = typename AlloT::template rebind_traits; auto elmAllo = adaptAllocator(); for (size_t idx=0; idxsubscript(idx)); + { + E* elm = reinterpret_cast (& bucket->subscript (idx)); + ElmAlloT::destroy (elmAllo, elm); + } } size_t storageBytes = Bucket::requiredStorage (bucket->buffSiz); std::byte* loc = reinterpret_cast (bucket); @@ -295,6 +298,34 @@ namespace lib { return move(*this); } + template + SeveralBuilder&& + appendAll (std::initializer_list ili) + { + using Val = typename meta::Strip::TypeReferred; + for (Val const& x : ili) + emplaceNewElm (x); + return move(*this); + } + + template + SeveralBuilder&& + fillElm (size_t cntNew, ARGS&& ...args) + { + for ( ; 0 (forward (args)...); + return move(*this); + } + + template + SeveralBuilder&& + emplace (ARGS&& ...args) + { + using Val = typename meta::RefTraits::Value; + emplaceNewElm (forward (args)...); + return move(*this); + } + /** * Terminal Builder: complete and lock the collection contents. @@ -307,18 +338,22 @@ namespace lib { return move (*this); } + size_t size() const { return Coll::size(); } + bool empty() const { return Coll::empty();} + + private: template void emplaceCopy (IT& dataSrc) { - using Val = std::remove_cv_t; - emplaceElm (*dataSrc); + using Val = typename IT::value_type; + emplaceNewElm (*dataSrc); } template void - emplaceElm (ARGS&& ...args) + emplaceNewElm (ARGS&& ...args) { // mark when target type is not trivially movable probeMoveCapability(); @@ -330,7 +365,7 @@ namespace lib { % util::typeStr() % reqSiz() % Coll::spread()}; // ensure sufficient storage or verify the ability to re-allocate - if (not (Coll::hasReserve(reqSiz()) + if (not (Coll::empty() or Coll::hasReserve(reqSiz()) or POL::canExpand(reqSiz()) or canDynGrow())) throw err::Invalid{_Fmt{"Unable to accommodate further element of type %s "} diff --git a/src/lib/test/test-coll.hpp b/src/lib/test/test-coll.hpp index 28360c786..39928d6a6 100644 --- a/src/lib/test/test-coll.hpp +++ b/src/lib/test/test-coll.hpp @@ -44,7 +44,7 @@ namespace test{ template inline VEC - getTestSeq_int(const uint NUM_ELMS) + getTestSeq_int (const uint NUM_ELMS) { VEC vec; for (uint i=0; i inline MAP - getTestMap_int(const uint NUM_ELMS) + getTestMap_int (const uint NUM_ELMS) { MAP map; for (uint i=0; i inline MUMAP - getTestMultiMap_int(const uint NUM_ELMS) + getTestMultiMap_int (const uint NUM_ELMS) { MUMAP map; for (uint i=0; igetVal()); - CHECK (check == ii->acc(+5) - 5); + CHECK (check == ii->calc(+5) - 5); --check; ++ii; } diff --git a/tests/library/scoped-collection-test.cpp b/tests/library/scoped-collection-test.cpp index 58f125786..3ee222783 100644 --- a/tests/library/scoped-collection-test.cpp +++ b/tests/library/scoped-collection-test.cpp @@ -55,12 +55,12 @@ namespace test{ * @param i when zero, the trigger value will be revealed */ virtual long - acc (int i) + calc (int i) { if (!i) return getVal() + trigger_; else - return Dummy::acc(i); + return Dummy::calc(i); } public: @@ -164,7 +164,7 @@ namespace test{ while (ii) { CHECK (check == ii->getVal()); - CHECK (check == ii->acc(+5) - 5); + CHECK (check == ii->calc(+5) - 5); ++check; ++ii; } @@ -262,11 +262,11 @@ namespace test{ CHECK (sum + rr == Dummy::checksum()); - CHECK (d0.acc(11) == coll[0].getVal() + 11 ); - CHECK (d1.acc(22) == rr + 22); - CHECK (d2.acc(33) == rr + 33); - CHECK (d2.acc(0) == rr + (rr+1) ); // SubDummy's special implementation of the acc()-function - // returns the trigger value, when the argument is zero + CHECK (d0.calc(11) == coll[0].getVal() + 11 ); + CHECK (d1.calc(22) == rr + 22); + CHECK (d2.calc(33) == rr + 33); + CHECK (d2.calc(0) == rr + (rr+1) ); // SubDummy's special implementation of the acc()-function + // returns the trigger value, when the argument is zero coll.clear(); coll.emplace (11,22); @@ -277,10 +277,10 @@ namespace test{ // NOTE DANGEROUS: // The previously obtained references just point into the object storage. // Thus we're now accessing a different object, even a different type! - CHECK (d0.acc(0) == 11 + 22); + CHECK (d0.calc(0) == 11 + 22); // The others even point into obsoleted storage holding zombie objects - CHECK (d1.acc(44) == rr + 44); + CHECK (d1.calc(44) == rr + 44); } CHECK (0 == Dummy::checksum()); @@ -317,12 +317,12 @@ namespace test{ CHECK (6 == coll.size()); CHECK (0 != Dummy::checksum()); - CHECK (coll[0].acc(0) == 0 + rr); - CHECK (coll[1].acc(0) == 1 + rr + trigger); - CHECK (coll[2].acc(0) == 2 + rr); - CHECK (coll[3].acc(0) == 3 + rr + trigger); - CHECK (coll[4].acc(0) == 4 + rr); - CHECK (coll[5].acc(0) == 5 + rr + trigger); + CHECK (coll[0].calc(0) == 0 + rr); + CHECK (coll[1].calc(0) == 1 + rr + trigger); + CHECK (coll[2].calc(0) == 2 + rr); + CHECK (coll[3].calc(0) == 3 + rr + trigger); + CHECK (coll[4].calc(0) == 4 + rr); + CHECK (coll[5].calc(0) == 5 + rr + trigger); // what does this check prove? // - the container was indeed populated with DubDummy objects // since the overridden version of Dummy::acc() did run and diff --git a/tests/library/scoped-holder-test.cpp b/tests/library/scoped-holder-test.cpp index baee6dbf0..f5ae27699 100644 --- a/tests/library/scoped-holder-test.cpp +++ b/tests/library/scoped-holder-test.cpp @@ -100,13 +100,13 @@ namespace test{ CHECK (0 < Dummy::checksum()); CHECK ( &(*holder)); - CHECK (holder->acc(2) == 2 + Dummy::checksum()); + CHECK (holder->calc(2) == 2 + Dummy::checksum()); Dummy *rawP = holder.get(); CHECK (rawP); CHECK (holder); CHECK (rawP == &(*holder)); - CHECK (rawP->acc(-5) == holder->acc(-5)); + CHECK (rawP->calc(-5) == holder->calc(-5)); TRACE (test, "holder at %p", &holder); TRACE (test, "object at %p", holder.get() ); @@ -214,7 +214,7 @@ namespace test{ HO & contained = maph[i]; CHECK (!contained); } // 100 holder objects created by sideeffect - // ..... without creating any contained object! + // ..... without creating any contained object! CHECK (0 == Dummy::checksum()); CHECK (!isnil (maph)); CHECK (100==maph.size()); @@ -223,13 +223,13 @@ namespace test{ { create_contained_object (maph[i]); CHECK (maph[i]); - CHECK (0 < maph[i]->acc(12)); + CHECK (0 < maph[i]->calc(12)); } CHECK (100==maph.size()); CHECK (0 != Dummy::checksum()); - long value55 = maph[55]->acc(0); + long value55 = maph[55]->calc(0); long currSum = Dummy::checksum(); CHECK (1 == maph.erase(55)); diff --git a/tests/library/scoped-holder-transfer-test.cpp b/tests/library/scoped-holder-transfer-test.cpp index d6b9abe60..438584833 100644 --- a/tests/library/scoped-holder-transfer-test.cpp +++ b/tests/library/scoped-holder-transfer-test.cpp @@ -147,7 +147,7 @@ namespace test { CHECK (rawP); CHECK (table[5]); CHECK (rawP == &(*table[5])); - CHECK (rawP->acc(-555) == table[5]->acc(-555)); + CHECK (rawP->calc(-555) == table[5]->calc(-555)); } CHECK (0 == Dummy::checksum()); } diff --git a/tests/library/several-builder-test.cpp b/tests/library/several-builder-test.cpp index f4190681b..8805a347b 100644 --- a/tests/library/several-builder-test.cpp +++ b/tests/library/several-builder-test.cpp @@ -28,6 +28,7 @@ #include "lib/test/run.hpp" #include "lib/test/testdummy.hpp" +#include "lib/test/test-coll.hpp" #include "lib/test/test-helper.hpp" #include "lib/test/diagnostic-output.hpp"////////////////TODO #include "lib/iter-explorer.hpp" @@ -44,6 +45,8 @@ using std::vector; using std::rand; using lib::explore; +using util::isLimited; +using util::isnil; using util::join; @@ -68,7 +71,7 @@ namespace test{ public: Num (uint seed=i) - : Dummy{seed} + : Dummy(seed) { ext_.fill(seed); setVal ((i+1)*seed); @@ -79,7 +82,7 @@ namespace test{ } long - acc (int ii) override + calc (int ii) override { return i+ii + explore(ext_).resultSum(); } @@ -110,7 +113,7 @@ namespace test{ simpleUsage(); check_Builder(); check_ErrorHandling(); - check_ElementAccess(); + check_ElementStorage(); check_CustomAllocator(); } @@ -131,11 +134,47 @@ namespace test{ /** @test TODO various ways to build an populate the container - * @todo WIP 6/24 🔁 define ⟶ implement + * @todo WIP 6/24 🔁 define ⟶ ✔ implement */ void check_Builder() { + SeveralBuilder builder; + CHECK (isnil (builder)); + + builder.emplace>() + .emplace>(1); + CHECK (2 == builder.size()); + builder.fillElm(2); + CHECK (4 == builder.size()); + builder.fillElm(3, 5); + CHECK (7 == builder.size()); + + Several elms = builder.build(); + CHECK ( isnil(builder)); + CHECK (not isnil(elms)); + CHECK (7 == elms.size()); +SHOW_EXPR(elms[0]) +SHOW_EXPR(elms[0].getVal()) + CHECK (elms[0].getVal() == (3+1)*3); // indeed a Num<3> with default-seed ≡ 3 +SHOW_EXPR(elms[0].calc(1)) + CHECK (elms[0].calc(1) == 3 + 1 + (3+3+3)); // indeed called the overridden calc() operation +SHOW_EXPR(elms[1].getVal()) + CHECK (elms[1].getVal() == (2+1)*1); // indeed a Num<2> with seed ≡ 1 +SHOW_EXPR(elms[1].calc(1)) + CHECK (elms[1].calc(1) == 2 + 1 + (1+1)); // indeed the overridden calc() picking from the Array(1,1) +SHOW_EXPR(elms[2].getVal()) + CHECK (isLimited (1, elms[2].getVal(), 100'000'000)); // indeed a Dummy with default random seed +SHOW_EXPR(elms[3].getVal()) + CHECK (isLimited (1, elms[3].getVal(), 100'000'000)); // and this one too, since we filled in two instances +SHOW_EXPR(elms[4].getVal()) + CHECK (elms[4].getVal() == 5); // followed by tree instances Dummy(5) +SHOW_EXPR(elms[5].getVal()) + CHECK (elms[5].getVal() == 5); +SHOW_EXPR(elms[6].getVal()) + CHECK (elms[6].getVal() == 5); +SHOW_EXPR(elms[6].calc(1)) + CHECK (elms[6].calc(1) == 5+1); // indeed invoking the base implementation of calc() } @@ -148,11 +187,11 @@ namespace test{ } - /** @test TODO verify access operations on the actual container + /** @test TODO verify correct placement of instances within storage * @todo WIP 6/24 🔁 define ⟶ implement */ void - check_ElementAccess() + check_ElementStorage() { } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index fe503f725..c57f8016c 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -54468,6 +54468,58 @@ + + + + + + + + + + + + + + +

+ STL-Iteratoren sind »abstrahierte Pointer« und setzen eigentlich die Idee eines Datencontainers vorraus. Das gilt nicht für Lumiera-Iteratoren; diese sind nicht dafür gedacht, Container-Inhalte  zu extrahieren oder zu manipulieren, sondern sie verkörpern eine Folge von Berechnungen. +

+ +
+
+ + + + +

+ ...und zwar vor allem im Zusammenspiel mit dem zentralen Konzept einer »State Core« — der Nutzer sollte nicht dazu verleitet werden, zu viele Annahmen über diesen State zu machen, ganz einfach, weil die Konsequenzen komplex und gedanklich schwer handhabbar sind. Das habe ich aus eigener Erfahrung gelernt (Stichwort der monadische Iter-Explorer) +

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

+ ...wäre eigentlich ganz billig zu haben, analog zu ConstIter +

+ +
+
+ + + +
@@ -81740,20 +81792,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

unser Storage-Puffer ist alignas(I) — das kann zu locker sein für das tatsächliche Objekt

- -
+ - - - +

Beispiel: I ist eine leere Klasse, hat also sizeof(I) ≡ alignof(I) ≡ 1 @@ -81768,8 +81815,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
⟹ die naive Lösung beginnt bei offset ≔ 3 und fügt für jeden Index +12 Bytes hinzu; damit sind alle Objekte grob falsch ausgerichtet

- -
+
@@ -81780,16 +81826,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

man könnte das nun ungenau oder punktgenau oder grob korrekt handhaben 

- -
+
@@ -81927,16 +81970,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Kann derzeit keine befriedigende Lösung für die diversen Zielkonflikte finden. Daher wähle ich eine Lösung, die Raum für zukünftige Lösungen schafft, und aktuell im Basisfall einfach und gradlinig zu implementieren ist. Speicher-Mehrverbrauch für die Metadaten wird in Kauf genommen

- -
+
@@ -81964,16 +82004,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

ein einfacher Pointer geht nicht, wegen dem anzuwendenden Spread

- -
+
@@ -81995,16 +82032,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

einfach ein lib::IterIndex<const Several<X>>

- -
+
@@ -82478,16 +82512,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

jetzt habe ich mich sowiso schon mal für die vorläufige Verschwendung entschieden

- -
+
@@ -82580,16 +82611,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...das mach die Verwendung einfacher (kein "template"-Präfix vor jeder Methode) und speziell das Deleter-λ kann nun direkt auf die geerbte Factory mit dem eingebetteten Allokator durchgreifen; ja der Code ist inzwischen riesengroß, aber alles hängt irgendwie mit allem zusammen, und ich sehe nicht, wie ich hier eine Teilkomponente so extrahieren könnte, daß der Code wirklich einfacher wird. Jedenfalls die separate Strategy-Klasse ist es nicht...

- -
+
@@ -82686,16 +82714,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

oder es wird ausschließlich der Element-Typ E konstruiert

- -
+
@@ -82707,16 +82732,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Vorsicht: hab es aus der emplaceElm()-Funktion entfernt

- -
+ @@ -82728,16 +82750,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...und das kann vom Allokator abhängen

- -
+
@@ -82791,16 +82810,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...man könnte geneigt sein, das zu überspringen; aber ich entscheide mich hier explizit dagegen, weil es gegen den Stil von C++ verstößt. Wenn man den Deleter-Aufruf vermeiden will, dann soll man eben triviale Objekte verwenden. Ende der Diskussion.

- -
+
@@ -82815,16 +82831,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Trick: capture der factory per value ⟹ Kopie des Allokators

- -
+
@@ -82861,31 +82874,25 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

das ist jetzt noch ein weiteres extra Datenfeld

- -
+ - - - +

denn der Allocation-Cluster weiß selber die Zahl seiner belegten Roh-Blöcke; zur de-Allokation muß ansonsten gar nichts gemacht werden

- -
+
@@ -82899,16 +82906,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Der vermutlich häufigste Fall ist ein Several<ProcNode*> — mit typischerweise genau einem Element mit einem »Slot«Größe. Dafür haben wir jetzt schon 4 »Slot« Overhead, und jetzt sollen das 5 »Slot« werden....

- -
+
@@ -82922,43 +82926,34 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...das heißt, wir verwenden im ersten »Slot« ein Bit-Feld, da die Zahl der Elemente ohnehin praktisch begrenzt ist (auf ein paar Hundert); in den freien oberen Bits kann daher das konkrete weitere Layout encodiert werden. Das kostet nur einen minimalen Runtime-Overhead (ein paar Bit-Manipulationen vor der Indirektion zum Datenelement, welches ebenfalls im Cache liegt). Der intendierte use-case nutzt diese Collection als Basis einer verzeigerten Datenstruktur, und nicht in einer innersten Loop.

- -
+
- - - +

besonderes geschickt: man kann das „später mal“ machen

- -
+ - - - +

Denn Lumiera hat im Moment wirklich andere Sorgen, als die Optimierung einer nocht-gar-nicht-wirklich-genutzten Datenstruktur

- -
+
@@ -82977,16 +82972,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

die grundsätzliche Möglichkeit der Anpassung ist bereits vorher sichergestellt, denn der veranlassende API-call wäre sonst schon per Exception abgebrochen worden

- -
+
@@ -83003,16 +82995,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Das ist eine Festlegung aus pragmatischen Gründen; weder der Container noch der Builder erfasst den Typ einzelner Elemente, daher gibt es auch keine Möglichkeit, überschüssigen Spread festzustellen. Zwar könnte der Client-Code diese Information besitzen, aber dann könnte er auch gleich richtig dimensionieren. Generell wird der Änderung des Spread keine besondere Bedeutung zugemessen — wenn es geht, wird's gemacht.

- -
+
@@ -83025,16 +83014,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

der Puffer sollte mit einem Initial-Wert beginnen, und dann in Verdoppelungs-Schriten wachsen; Verdoppelung setzt natürlich Kenntnis der aktuellen Puffergröße voraus; dabei gäbe es aber auch noch einen Maximalwert zu beachten, der vom Allokator abhängen kann.

- -
+
@@ -83079,6 +83065,27 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + + + + + + + + @@ -83096,16 +83103,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...aufgrund der Flexibilität ist das intendierte Verhalten nicht mehr einfach zu fassen; es ist eigens ein Zugangsweg zu finden, um die richtige Logik zu entwickeln---

- -
+
@@ -83176,9 +83180,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Vorsicht Falle! @@ -83187,8 +83189,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
Ohne diesen Check würden wir uns in den Fuß schießen, wenn wir den Container in den wild-move-Modus schalten, denn Elemente vom Typ E oder I könnten stets ohne Weiteres noch dazukommen

- -
+
@@ -83257,16 +83258,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...denn die default-Impl kopiert skalare Typen lediglich; hier müssen wir sie aber wirklich austauschen, damit nur eine Instanz den aktiven Pointer hält...

- -
+
@@ -83276,16 +83274,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

storageBytes wird nur in der Factory benötigt und ist die Größe der gesamten Allokation incl Admin-Overhead

- -
+
@@ -83294,16 +83289,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

....und das wird praktisch immer greifen, wenn Element-Typ und Interface-Typ identisch sind; es steht zu befürchten daß deshalb der triviale destruktur praktisch nie zum Tragen kommt. Anders herum bestünde auch keine Gefahr, daß ein erstes Element, das auch ein Subtyp sein könnte, eine Entscheidung für TRIVIAL fällen würde, denn der ELEMENT-Fall fordert ja grade, daß alle Elemente den gleichen Typ haben; also wäre eine solche Festlegung auch in diesem Fall sogar vorteilhaft, da sie den Destruktor-Aufruf einspart

- -
+
@@ -83316,11 +83308,26 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + + + + + + + + + + + +