Library: build a helper to encapsulate container access by index

...mostly we want the usual convenient handling pattern for iterators,
but with the proviso actually to perform an access by subscript,
and the ability to re-set to another current index
This commit is contained in:
Fischlurch 2024-03-20 23:58:42 +01:00
parent 76bd9ba6ce
commit f716fb0bee
3 changed files with 506 additions and 15 deletions

144
src/lib/iter-index.hpp Normal file
View file

@ -0,0 +1,144 @@
/*
ITER-INDEX.hpp - iterator with indexed random-access to referred container
Copyright (C) Lumiera.org
2024, Hermann Vosseler <Ichthyostega@web.de>
This program 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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file iter-index.hpp
** Iterator-style access handle to a referred container with subscript index.
** This wrapper packages a current index number and a back-link to some data container
** with subscript operator and range check. This allows to hand out a navigable access point
** to a processing algorithm while abstracting away the actual data storage. Besides usage
** as »Lumiera Forward Iterator«, the current access position can be retrieved directly
** and it can be relocated to another valid index position; this implies also the ability
** to re-set the iteration to the container's start.
**
** @see IterIndex_test
** @see iter-adapter.hpp
** @see [usage example](\ref lib::TextTemplate::InstanceCore)
**
*/
#ifndef SRC_LIB_ITER_INDEX_H
#define SRC_LIB_ITER_INDEX_H
#include "lib/iter-adapter.hpp"
//#include <type_traits>
//#include <utility>
namespace lib {
namespace {// Implementation of the access core
template<typename PTR>
struct IndexAccessCore
{
PTR data_{};
size_t idx_{0};
using ResVal = decltype(data_->operator[](0));
using value_type = typename meta::RefTraits<ResVal>::Value;
using reference = typename meta::RefTraits<ResVal>::Reference;
using IterWrapper = lib::IterStateWrapper<value_type, IndexAccessCore>;
bool
checkPoint() const
{
return isValidIDX(idx_);
}
reference
yield() const
{
return (*data_)[idx_];
}
void
iterNext()
{
++idx_;
}
bool
isValidIDX (size_t idx) const
{
return bool(data_)
and idx < data_->size();
}
friend bool operator== (IndexAccessCore const& c1, IndexAccessCore const& c2)
{
return c1.data_ == c2.data_ and (not c1.data_ or c1.idx_ == c2.idx_);
}
friend bool operator!= (IndexAccessCore const& c1, IndexAccessCore const& c2)
{
return not (c1 == c2);
}
};
}//(End)Implementation
/**
*
*/
template<class CON, typename PTR = CON*>
class IterIndex
: public IndexAccessCore<PTR>::IterWrapper
{
using _Cor = IndexAccessCore<PTR>;
using _Par = typename _Cor::IterWrapper;
public:
IterIndex() = default;
IterIndex (CON& dataContainer)
: _Par{_Cor{&dataContainer, 0}}
{ }
size_t
getIDX() const
{
_Par::__throw_if_empty();
return const_cast<IterIndex*>(this)->stateCore().idx_;
}
void
setIDX (size_t newIDX)
{
auto& core = _Par::stateCore();
if (not core.isValidIDX (newIDX))
throw lumiera::error::Invalid ("Attempt to set index out of bounds",
lumiera::error::LUMIERA_ERROR_INDEX_BOUNDS);
core.idx_ = newIDX;
}
};
} // namespace lib
#endif /*SRC_LIB_ITER_INDEX_H*/

View file

@ -0,0 +1,265 @@
/*
IterIndex(Test) - verify index access packaged as iterator handle
Copyright (C) Lumiera.org
2024, Hermann Vosseler <Ichthyostega@web.de>
This program 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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
/** @file iter-index-test.cpp
** unit test \ref IterIndex_test
*/
#include "lib/test/run.hpp"
#include "lib/iter-index.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/iter-explorer.hpp"
#include "lib/format-util.hpp"
#include "lib/util.hpp"
#include <vector>
namespace lib {
namespace test{
using ::Test;
using util::join;
using util::isnil;
using std::vector;
using LERR_(ITER_EXHAUST);
using LERR_(INDEX_BOUNDS);
namespace { // test fixture
const uint NUM_ELMS = 10;
using Numz = vector<uint>;
using Iter = IterIndex<Numz>;
using CIter = IterIndex<const Numz>;
inline Numz
makeNumz()
{
Numz numz;
for (uint i=0; i<NUM_ELMS; ++i)
numz.push_back(i);
return numz;
}
} // (END)fixture
/*****************************************************************//**
* @test demonstrate and cover the properties of IterCursor.
* This wrapper allows to change between iterating forward and backwards.
*
* @see iter-cursor.hpp
* @see iter-adapter.hpp
* @see [usage example](\ref event-log.hpp)
*/
class IterIndex_test : public Test
{
virtual void
run (Arg)
{
simpleIteration();
verify_randomAccess();
iterTypeVariations();
}
/** @test just iterate in various ways. */
void
simpleIteration ()
{
Numz numz{makeNumz()};
Iter i1{numz};
CHECK (not isnil(i1));
CHECK (0 == *i1);
++++++i1;
CHECK (3 == *i1);
for (uint i=*i1 ; i1; ++i1, ++i)
CHECK (i == *i1);
CHECK (isnil(i1));
auto sum = explore(Iter{numz}).resultSum();
uint n = numz.size() - 1;
CHECK (sum == n*(n+1)/2);
for (auto & i : Iter{numz})
++i; // note: manipulate the contents...
CHECK (join(numz,"") == "1◇2◇3◇4◇5◇6◇7◇8◇9◇10"_expect);
verifyComparisons (Iter{numz});
}
/** @test verify the ability of IterCursor to switch
* the direction of the iteration. This "gear switch" can be done
* any time, while in the middle of iteration, and even after
* iteration end. That means, even an exhausted iterator can be
* "turned back". This does not work on a default constructed
* IterCursor, though.
*/
void
verify_randomAccess ()
{
Numz numz{makeNumz()};
Iter iter{numz};
CHECK (0 == *iter);
++++++++iter;
CHECK (4 == *iter);
CHECK (not isnil(iter));
CHECK (join(iter) == "4, 5, 6, 7, 8, 9"_expect);
verifyComparisons (iter);
CHECK (4 == *iter);
CHECK (4 == iter.getIDX());
iter.setIDX(7);
CHECK (7 == iter.getIDX());
CHECK (not isnil(iter));
CHECK (7 == *iter);
++iter;
CHECK (8 == *iter);
iter.setIDX(6);
CHECK (join(iter) == "6, 7, 8, 9"_expect);
verifyComparisons (iter);
++++++++iter;
CHECK (isnil(iter));
VERIFY_ERROR (ITER_EXHAUST, *iter);
VERIFY_ERROR (ITER_EXHAUST, ++iter);
VERIFY_ERROR (ITER_EXHAUST, iter.getIDX());
iter.setIDX(9);
CHECK (not isnil(iter));
CHECK (9 == *iter);
VERIFY_ERROR (INDEX_BOUNDS, iter.setIDX(10));
CHECK (9 == iter.getIDX());
VERIFY_ERROR (INDEX_BOUNDS, iter.setIDX(-1));
CHECK (9 == iter.getIDX());
}
/** @test verify the const and dereferencing variants,
* based on the const-ness of the underlying STL iterator
*/
void
iterTypeVariations ()
{
Numz numz{makeNumz()};
Numz const& const_numz{numz};
uint i = 0;
for (Iter iter{numz};
iter; ++iter, ++i
)
{
CHECK (iter);
CHECK (iter != Iter());
CHECK (*iter == i);
--(*iter);
CHECK (*iter == i-1);
}
i = 0;
for (CIter iter{const_numz};
iter; ++iter, ++i
)
{
CHECK (iter);
CHECK (iter != CIter());
CHECK (*iter == i-1);
// note: the previous run indeed modified
// the elements within the container.
// ++(*iter); // doesn't compile, because it yields a "* const"
}
verifyComparisons (CIter{numz});
}
/** @test verify equality handling and NIL detection
* for the given iterator/wrapper handed in.
* @note the argument is not altered; rather we create
* several copies, to iterate and compare those
*/
template<class IT>
void
verifyComparisons (IT const& ii)
{
IT i1(ii);
IT i2(ii);
IT iN;
CHECK ( isnil (iN));
CHECK (!isnil (i1));
CHECK (!isnil (i2));
CHECK (i1 == i2); CHECK (i2 == i1);
CHECK (i1 != iN); CHECK (iN != i1);
CHECK (i2 != iN); CHECK (iN != i2);
++i1;
CHECK (i1 != i2);
CHECK (i1 != iN);
++i2;
CHECK (i1 == i2);
CHECK (i1 != iN);
CHECK (i2 != iN);
while (++i1) { }
CHECK (isnil(i1));
CHECK (i1 != i2);
CHECK (i1 == iN);
while (++i2) { }
CHECK (isnil(i2));
CHECK (i2 == i1);
CHECK (i2 == iN);
}
};
LAUNCHER (IterIndex_test, "unit common");
}} // namespace lib::test

View file

@ -112553,16 +112553,13 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
<node CREATED="1710888899301" ID="ID_1431488282" MODIFIED="1710889348598" TEXT="mu&#xdf; zuweisbar sein (also letztlich ein Pointer auf die Daten)"/>
<node CREATED="1710889358377" ID="ID_903149258" MODIFIED="1710889479699" TEXT="wird rekursiv / re-entrant verwendet">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
das bedeutet: nachdem ein nested-context <i>ge&#246;ffnet wurde, </i>m&#252;ssen wir einen State erlangen, auf dem transparent genauso gearbeitet werden kann, wie auf dem initialen / top-level-State
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<icon BUILTIN="messagebox_warning"/>
</node>
</node>
@ -112577,16 +112574,13 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
<node CREATED="1710888800147" ID="ID_415681901" MODIFIED="1710888817940" TEXT="konzeptionell wie eine Dreferentiation"/>
<node CREATED="1710888818840" ID="ID_480044105" MODIFIED="1710888865476" TEXT="aber die Implementierung obliegt der DataSrc">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
weil sie im Einzelfall auch komplexer sein kann, und im Besonderen eine parent-Verkn&#252;pfung beinhaltet
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
</node>
</node>
@ -112846,6 +112840,7 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
<node CREATED="1710802648966" ID="ID_307257971" MODIFIED="1710802653691" TEXT="sequence-extractor"/>
</node>
<node CREATED="1710802684313" ID="ID_1755236197" MODIFIED="1710802692132" TEXT="currInstruction">
<arrowlink COLOR="#b43057" DESTINATION="ID_647695428" ENDARROW="Default" ENDINCLINATION="-527;-17;" ID="Arrow_ID_660745747" STARTARROW="None" STARTINCLINATION="211;249;"/>
<node CREATED="1710802696248" ID="ID_1500495122" MODIFIED="1710802702482" TEXT="der instruction pointer"/>
<node CREATED="1710802707078" ID="ID_1676117590" MODIFIED="1710802716696" TEXT="ist zuweisbar (random-access)"/>
</node>
@ -112867,16 +112862,13 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
<node CREATED="1710889546263" ID="ID_1579638728" MODIFIED="1710889557098" TEXT="einschlie&#xdf;lich der M&#xf6;glichkeit verschachtelter States"/>
<node CREATED="1710889557878" ID="ID_1797783109" MODIFIED="1710889645948" TEXT="letzteres ist aber als seltene/optionale Erweiterung anzusetzen">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
will sagen, der Standardfall ist, lediglich auf eine Map per Key zuzugreifen &#8212; und die gesamte Datenstruktur ist hierauf zu optimieren
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
<node CREATED="1710859059558" ID="ID_878052078" MODIFIED="1710889700823" TEXT="eingebettete DataSrc &#x2261; das ist das konkrete Binding">
<node CREATED="1710859078771" ID="ID_283747990" MODIFIED="1710889722804" TEXT="ist zuweisbar (f&#xfc;r nesting)"/>
@ -112908,6 +112900,13 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
</node>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1710898830750" ID="ID_629945091" MODIFIED="1710898839421" TEXT="ist das auch praktisch machbar?">
<icon BUILTIN="help"/>
<node CREATED="1710977238360" ID="ID_1614727391" MODIFIED="1710977265505" TEXT="sehr wohl &#x2014; genau daf&#xfc;r wurde std::string_view eingef&#xfc;hrt">
<icon BUILTIN="idea"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1710977296336" ID="ID_359874280" MODIFIED="1710986480717" TEXT="Vorsicht: string-view ist inh&#xe4;rent gef&#xe4;hrlich">
<arrowlink COLOR="#7b415c" DESTINATION="ID_860112408" ENDARROW="Default" ENDINCLINATION="-3082;815;" ID="Arrow_ID_1628394674" STARTARROW="None" STARTINCLINATION="-1036;64;"/>
<icon BUILTIN="messagebox_warning"/>
</node>
</node>
</node>
<node CREATED="1710901352781" ID="ID_98335183" MODIFIED="1710901434035" TEXT="mu&#xdf; es schon deshalb machen, weil das Rendern einmalig bei der Iteration passiert">
@ -112940,6 +112939,64 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710978786840" ID="ID_647695428" MODIFIED="1710978844422" TEXT="brauche einen &#xbb;Cursor&#xab; f&#xfc;r aktuelle Action">
<linktarget COLOR="#b43057" DESTINATION="ID_647695428" ENDARROW="Default" ENDINCLINATION="-527;-17;" ID="Arrow_ID_660745747" SOURCE="ID_1755236197" STARTARROW="None" STARTINCLINATION="211;249;"/>
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1710978847288" ID="ID_1081341248" MODIFIED="1710978855055" TEXT="hatte ich sowas nicht schon mal?">
<icon BUILTIN="help"/>
<node CREATED="1710979039184" ID="ID_1895789363" LINK="#ID_1801538785" MODIFIED="1710979356636" TEXT="CursorGear">
<icon BUILTIN="help"/>
<icon BUILTIN="button_cancel"/>
<node CREATED="1710979082768" ID="ID_369083585" MODIFIED="1710979103721" TEXT="IterCursor_test"/>
<node CREATED="1710979301630" ID="ID_46182414" MODIFIED="1710979328828" TEXT="der hei&#xdf;t zwar &#xbb;cursor&#xab; &#x2014; ist aber nur ein vorw&#xe4;rts/r&#xfc;ckw&#xe4;rts-Iterator"/>
<node CREATED="1710979329631" ID="ID_114803277" MODIFIED="1710979349848" TEXT="bildet die Basis f&#xfc;r Suchoperationen auf dem EventLog"/>
</node>
<node CREATED="1710979380360" ID="ID_614355480" MODIFIED="1710979390858" TEXT="kann mich sonst nicht an sowas erinnern"/>
<node CREATED="1710979395354" ID="ID_1076235199" MODIFIED="1710979402633" TEXT="widerspricht auch dem Iterator-Ansatz"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1710979423805" ID="ID_1010060877" MODIFIED="1710979484395" TEXT="Iterator gen&#xfc;gt nicht &#x2014; brauche back-Link auf den Basis-Container">
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1710979486244" ID="ID_443568794" MODIFIED="1710979497911" TEXT="was ich hier brauche ist einfach &#x2014; und generisch">
<icon BUILTIN="idea"/>
</node>
<node COLOR="#338800" CREATED="1710979647405" ID="ID_635270252" MODIFIED="1710986040853" TEXT="IterIndex&lt;CON&gt;">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1710980823626" ID="ID_1159027894" MODIFIED="1710986027221" TEXT="Struktur: ein State-Wrapper">
<icon BUILTIN="button_ok"/>
<node CREATED="1710980870121" ID="ID_1036626151" MODIFIED="1710980881428" TEXT="Gr&#xf6;&#xdf;e : 2 &#xbb;Slot&#xab;"/>
<node CREATED="1710980885639" ID="ID_1978510735" MODIFIED="1710980895665" TEXT="bettet die Index-Nummer direkt ein"/>
<node CREATED="1710980896470" ID="ID_1734087052" MODIFIED="1710980917374" TEXT="Pointer-Typ konfigurierbar machen (falls Ownership gew&#xfc;nscht)"/>
</node>
<node COLOR="#338800" CREATED="1710981200357" ID="ID_596111172" MODIFIED="1710986024544" TEXT="brauche separate Core als Hilfsklasse">
<icon BUILTIN="button_ok"/>
<node CREATED="1710981216498" ID="ID_1250106024" MODIFIED="1710981224270" TEXT="auch wegen der Typ-Inferenz"/>
<node CREATED="1710983273680" ID="ID_1670736861" MODIFIED="1710983309559" TEXT="einmal kreuzweise typedefs + decltype()"/>
<node BACKGROUND_COLOR="#c8c0b6" CREATED="1710983310267" ID="ID_268611095" MODIFIED="1710983344686">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
uuund fertig &#8212; <b>C++ ist toll</b>
</p>
</body>
</html></richcontent>
<icon BUILTIN="ksmiletris"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1710983365347" ID="ID_12713467" MODIFIED="1710986045325" TEXT="IterIndex_test">
<icon BUILTIN="pencil"/>
<node CREATED="1710983370851" ID="ID_929461554" MODIFIED="1710983390046" TEXT="verwende IterCursor_text als Vorlage">
<icon BUILTIN="idea"/>
<node CREATED="1710983391672" ID="ID_201237845" MODIFIED="1710983405226" TEXT="der deckt n&#xe4;mlich eine ganze Menge ekelhafte Randf&#xe4;lle mit ab"/>
<node CREATED="1710983406078" ID="ID_252133900" MODIFIED="1710983436747" TEXT="auch die const-correctness und State-cloning"/>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1710986046845" ID="ID_1980706468" MODIFIED="1710986077126" TEXT="sollte noch eine smart-Ptr-Variante mit abdecken">
<icon BUILTIN="bell"/>
</node>
</node>
</node>
</node>
</node>
<node CREATED="1710856784967" ID="ID_201927694" MODIFIED="1710856798759" TEXT="Parsing soll eager sein (wegen Syntax-Fehlern)"/>
</node>
@ -120672,6 +120729,31 @@ class Something
</node>
</node>
</node>
<node CREATED="1710977317941" ID="ID_1453026433" MODIFIED="1710977322814" TEXT="std::string_view">
<node CREATED="1710977334174" ID="ID_1310001368" MODIFIED="1710977350088" TEXT="ist eine read-only-View in ein char-Array"/>
<node CREATED="1710977354168" ID="ID_184856921" MODIFIED="1710977365523" TEXT="ist explizit f&#xfc;r Performance-Optimierung gedacht"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1710977367862" ID="ID_860112408" MODIFIED="1710986482731" TEXT="Gefahren">
<linktarget COLOR="#7b415c" DESTINATION="ID_860112408" ENDARROW="Default" ENDINCLINATION="-3082;815;" ID="Arrow_ID_1628394674" SOURCE="ID_359874280" STARTARROW="None" STARTINCLINATION="-1036;64;"/>
<node CREATED="1710977372915" ID="ID_1575898732" MODIFIED="1710977488625" TEXT="dangling references">
<icon BUILTIN="clanbomber"/>
<node CREATED="1710977449251" ID="ID_1055400302" MODIFIED="1710977453942" TEXT="Lebensdauer der Basis-Daten beachten"/>
<node CREATED="1710977455010" ID="ID_402913842" MODIFIED="1710977485628" TEXT="verborgener Pointer: Gefahr bei Inline-Daten, die verschoben / realloziert werden"/>
</node>
<node CREATED="1710977381021" ID="ID_180009098" MODIFIED="1710977501175">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
Konversion string_view &#10236; string <b>kopiert</b>&#160;die Daten
</p>
</body>
</html></richcontent>
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1710977502025" ID="ID_766667745" MODIFIED="1710977510502" TEXT="deshalb ist diese Konversion explizit"/>
<node CREATED="1710977511069" ID="ID_1874799796" MODIFIED="1710977520557" TEXT="(wor&#xfc;ber sich viele Leute aufgeregt haben)"/>
</node>
</node>
</node>
</node>
<node CREATED="1636143147825" ID="ID_224980157" MODIFIED="1636143159589" TEXT="Assembly">
<node CREATED="1636143161969" ID="ID_1119605136" MODIFIED="1636143179445" TEXT="r?x : 64-Bit-Register"/>