From fea2bfde7a32094fc19d67bf1fb5a6e4eb946ff0 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 12 Dec 2024 23:27:10 +0100 Subject: [PATCH] Invocation: complete helper for chained inline tuples - complete documentation - add extensive test coverage for use of the accessors - demonstrate a more contrieved example, including the dangers --- src/lib/hetero-data.hpp | 87 ++++++++++----- src/steam/engine/turnout-system.hpp | 1 + tests/15library.tests | 2 +- tests/library/hetero-data-test.cpp | 164 +++++++++++++++++++++++++++- wiki/thinkPad.ichthyo.mm | 65 +++++------ 5 files changed, 252 insertions(+), 67 deletions(-) diff --git a/src/lib/hetero-data.hpp b/src/lib/hetero-data.hpp index 574a6a3c6..25d273611 100644 --- a/src/lib/hetero-data.hpp +++ b/src/lib/hetero-data.hpp @@ -45,13 +45,13 @@ ** auto h1 = Front::build (1,2.3); ** using Cons1 = Front::Chain; ** auto b2 = Cons1::build (true, "Ψ"); - ** b2.linkInto(c1); + ** b2.linkInto(h1); ** auto& [d1,d2,d3,d4] = Cons1::recast(h1); ** CHECK (d1 == 1); ** CHECK (d2 == 2.3); ** CHECK (d3 == true); ** CHECK (d4 == "Ψ"); - ** Cons1::Accessor get4; + ** Cons1::AccessorFor get4; ** CHECK (get4(h1) == "Ψ"); ** \endcode ** @@ -69,17 +69,12 @@ #include "lib/error.hpp" -//#include "lib/symbol.hpp" #include "lib/nocopy.hpp" -//#include "lib/linked-elements.hpp" #include "lib/meta/typelist.hpp" #include "lib/meta/typelist-manip.hpp" -//#include "lib/meta/typelist-util.hpp" +#include "lib/meta/typelist-util.hpp" #include "lib/meta/typeseq-util.hpp" -#include "lib/test/test-helper.hpp" -//#include -//#include #include #include @@ -115,6 +110,9 @@ namespace lib { template void linkInto (HeteroData&); + + template auto& get() noexcept { return std::get(*this); } + template auto& get() noexcept { return std::get(*this); } }; @@ -149,11 +147,26 @@ namespace lib { return * reinterpret_cast<_Tail*> (Frame::next); } + template + static _Self& + recast (HeteroData& frontChain) + { + return reinterpret_cast<_Self&> (frontChain); + } + template + static _Self const& + recast (HeteroData const& frontChain) + { + return reinterpret_cast<_Self const&> (frontChain); + } + + template friend class HeteroData; ///< allow chained types to use recursive type definitions using Frame::Frame; ///< data elements shall be populated through the builder front-ends + public: HeteroData() = default; @@ -163,9 +176,11 @@ namespace lib { return localSiz + _Tail::size(); } + /** access type to reside in the given slot of the _complete chain_ */ template using Elm_t = typename PickType::type; + /** access data elements within _complete chain_ by index pos */ template Elm_t& @@ -185,48 +200,68 @@ namespace lib { return const_cast(this)->get(); } + + /** + * Accessor-functor to get at the data residing within some tuple element + * Using the enclosing typed scope to ensure safe storage access + * @tparam slot numer of the data element, counting from zero over the full chain + * @note this functor holds no data, but shall be applied to some existing HeteroData. + */ template struct Accessor { using Type = Elm_t; template - Type& + static Type& get (HeteroData& frontEnd) { - auto& fullChain = reinterpret_cast<_Self&> (frontEnd); + auto& fullChain = _Self::recast (frontEnd); return fullChain.template get(); } + + template + Type& operator() (HH& frontEnd) const { return Accessor::get(frontEnd); } }; + /** + * Constructor-functor to build an extra data segment, which can then be linked to the chain. + * @tparam VALS data types to use in the extra storage tuple + * @note Using this functor is the only safe path to create and add new data blocks. + * Each such data block can be linked in once, and only if the base chain matches + * the structure embedded into the type of the enclosing scope. + * - storage frames can be default constructed, but not copied / moved thereafter + * - the #build() function can be used to create the block and init the data + * - after creating a frame, it must be explicitly linked in by invoking NewFrame::linkInto() + * - the #recast() function will re-interpret _any_ `HeteroData&` into the storage structure + * which can be expected after building the extension frame (use with care!) + * - the nested template ChainExtent is a follow-up constructor-functor to add a further block + * - the nested template Accessor shall be used for any type-save access to data values + * - if all types are distinct, the Accessor can also be selected by-type + */ template struct Chain { - using NewFrame = StorageFrame; - using ChainType = HeteroData,NewFrame>::List>; - + using Segments = meta::Node; // ◁———this type describes current chain structure + using NewFrame = StorageFrame::value, VALS...>; + using ChainType = HeteroData::List>; + // ...and this would be the extended chain structure template static NewFrame build (INIT&& ...initArgs) { - return {initArgs ...}; + return {initArgs ...}; // Note: NewFrame is non-copyable } - template - static ChainType& - recast (HeteroData& frontChain) + template + static auto& + recast (HET& frontChain) { - return reinterpret_cast (frontChain); - } - template - static ChainType& - recast (HeteroData const& frontChain) - { - return reinterpret_cast (frontChain); + return ChainType::recast (frontChain); } template - using ChainExtension = typename ChainType::template Chain; + using ChainExtent = typename ChainType::template Chain; template using Accessor = typename ChainType::template Accessor<_Self::size()+slot>; @@ -306,7 +341,7 @@ namespace lib { , segments); return last->next; } - } + }//(End)helper /** diff --git a/src/steam/engine/turnout-system.hpp b/src/steam/engine/turnout-system.hpp index 2573f5323..f878b0472 100644 --- a/src/steam/engine/turnout-system.hpp +++ b/src/steam/engine/turnout-system.hpp @@ -41,6 +41,7 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1367 : Rebuild the Node Invocation #include "lib/nocopy.hpp" #include "lib/time/timevalue.hpp" +#include "lib/hetero-data.hpp" namespace steam { diff --git a/tests/15library.tests b/tests/15library.tests index c26196ef3..9730acd5f 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -434,7 +434,7 @@ return: 0 END -PLANNED "Heterogeneous data in local storage" HeteroData_test < @@ -31,15 +32,20 @@ namespace test{ using std::string; using meta::is_Subclass; using util::isSameObject; + using util::isSameAdr; + using util::getAdr; - - /*************************************************************//** + /******************************************************************//** * @test maintain a sequence of data tuples in local storage, * providing pre-configured type-safe data access. - * + * - the initial block is just a tuple of data in local storage + * - but further extension segments can be created _elsewhere_ + * and attached to an existing chain + * - a compile-time »overlay« of constructor- and accessor-functors + * is provided as _guard rails_ to prevent out-of bounds access. * @see lib::HeteroData * @see NodeBase_test::verify_TurnoutSystem() */ @@ -49,9 +55,23 @@ namespace test{ virtual void run (Arg) { + simpleUsage(); verify_FrontBlock(); verify_ChainBlock(); -// verify_Accessors(); + verify_Accessors(); + } + + + void + simpleUsage() + { + using F = lib::HeteroData; // define type of the front-end segment + auto h1 = F::build (1,2.3); // build the front-end, including first data tuple + using C = F::Chain; // define a constructor type for a follow-up segment + auto b2 = C::build (true, "Ψ"); // build this follow-up segment free-standing + b2.linkInto(h1); // link it as second segment into the chain + C::AccessorFor get4; // get an accessor functor (picked by value type) + CHECK (get4(h1) == "Ψ"); // use accessor on front-type (involves force-cast) } @@ -152,6 +172,140 @@ namespace test{ // auto& [z0,z1,z2,z3] = chain2; // Error: 4 names provided for structured binding, while HeteroData... decomposes into 3 elements // auto& [z0,z1,z2] = b1; // Error: HeteroData, NullType> >' decomposes into 1 element } + + + + /** @test demonstrate elaborate storage layout with several chain frames + * - follow-up frames shall be built using constructor types + * - these can be defined prior to any data allocation + * - individual data elements can be accessed type-safe through accessor functors + * @warning as demonstrated, this is a dangerous bare-bone memory layout without runtime checks! + */ + void + verify_Accessors() + { + using Front = lib::HeteroData; + using Cons2 = Front::Chain; + using Data2 = Cons2::NewFrame; + using List2 = Cons2::ChainType; + using Acc4 = Cons2::AccessorFor; + using Acc3 = Cons2::AccessorFor; + using Acc2 = Front::Accessor<1>; + using Acc1 = Front::Accessor<0>; + using Cons3 = Cons2::ChainExtent; + using Data3 = Cons3::NewFrame; + using List3 = Cons3::ChainType; + using Acc5 = Cons3::AccessorFor; + using Acc6 = Cons3::AccessorFor; + CHECK (2 == Front::size()); + CHECK (4 == List2::size()); + CHECK (6 == List3::size()); + // + // Note: up to now, not a single actual data element has been created + // Moreover, individual blocks can be created in any order... + Data2 d2; + d2.get<1>() = "Ψ"; + Front front; + CHECK (front.get<1>() == 0.0); + front.get<1>() = 2.3; + + // Note the pitfall: Chain has not been connected yet, + // but the Accessors would assume otherwise + CHECK (Acc2::get(front) == 2.3); +// Acc3::get(front); // would cause NPE (or assertion failure on debug build) + + Acc4 get4; // could even instantiate the accessors... + CHECK (sizeof(get4) == 1); // (empty marker object with static methods) +// get4(front); // likewise NPE or assertion fail + + // Now link the second data element in properly + d2.linkInto(front); + CHECK (Acc1::get(front) == 0); + CHECK (Acc2::get(front) == 2.3); + CHECK (Acc3::get(front) == false); + CHECK (get4(front) == "Ψ"); + + // further allocations can even be »elsewhere« + const void* loc; + { + Acc6 get6; + auto magic = Cons3::build("magic","cloud"); + loc = getAdr(magic); + CHECK (magic.get<0>() == "magic"_expect); + CHECK (magic.get<1>() == "cloud"_expect); + // link into the cloud... + magic.linkInto(front); + CHECK (get6(front) == "cloud"); + }// aaand... + // it's gone + + // Evil, evil... + lib::UninitialisedStorage evilSpace; + Data3& d3 = evilSpace[0]; // note: working with left-over data from expired stack frame + CHECK (isSameAdr (d3, loc)); + CHECK (d3.get<0>() == "magic"_expect); // const char* points into static data, so the chars are still there + new(&d3.get<1>()) string{"mushrooms"}; // the "cloud"-string was destroyed by magic's destructor + + auto& [v1,v2,v3,v4,v5,v6] = Cons3::recast(front); // using connectivity from the linked list connecting the segments + CHECK (v1 == "0"_expect); + CHECK (v2 == "2.3"_expect); + CHECK (v3 == "false"_expect); + CHECK (v4 == "Ψ"_expect); + CHECK (v5 == "magic"_expect); + CHECK (v6 == "mushrooms"_expect); + + v1 = 42; + v2 = 5.5; + v3 = true; + CHECK (front.get<0>() == 42); + CHECK (front.get<1>() == 5.5); + CHECK (d2.get<0>() == true); + CHECK (d2.get<1>() == "Ψ"); + + CHECK (isSameAdr (Acc1::get(front), v1)); + CHECK (isSameAdr (Acc2::get(front), v2)); + CHECK (isSameAdr (Acc3::get(front), v3)); + CHECK (isSameAdr (Acc4::get(front), v4)); + CHECK (isSameAdr (Acc5::get(front), v5)); + CHECK (isSameAdr (Acc6::get(front), v6)); + + CHECK (not isSameAdr (front, v1)); + CHECK (not isSameAdr (d2, v3)); + CHECK (not isSameAdr (d3, v5)); + // we can directly re-cast into another typed front-end + List3& fullChain = Cons3::recast(front); + CHECK (isSameAdr (fullChain.get<2>(), std::get<0>(d2))); + CHECK (isSameAdr (fullChain.get<3>(), std::get<1>(d2))); + CHECK (isSameAdr (fullChain.get<4>(), std::get<0>(d3))); + CHECK (isSameAdr (fullChain.get<5>(), std::get<1>(d3))); + CHECK (isSameAdr (fullChain.get<0>(), v1)); + CHECK (isSameAdr (fullChain.get<1>(), v2)); + CHECK (isSameAdr (fullChain.get<2>(), v3)); + CHECK (isSameAdr (fullChain.get<3>(), v4)); + CHECK (isSameAdr (fullChain.get<4>(), v5)); + CHECK (isSameAdr (fullChain.get<5>(), v6)); + // we can even use partially specified chains + List2& partChain = Cons2::recast(fullChain); + CHECK (isSameAdr (partChain.get<0>(), v1)); + CHECK (isSameAdr (partChain.get<1>(), v2)); + CHECK (isSameAdr (partChain.get<2>(), v3)); + CHECK (isSameAdr (partChain.get<3>(), v4)); + + // Note: basically we are still using stale memory, + // previously allocated to the "magic" block, + // and now covered by the UninitialisedStorage + CHECK (loc == & d3); + CHECK (loc < & v5); + CHECK (loc < & v6); + + // structural binding on partial chains is limited + CHECK (partChain.size() == 4); + auto& [w1,w2,w3,w4] = partChain; + CHECK (isSameObject (v1, w1)); + CHECK (isSameObject (v2, w2)); + CHECK (isSameObject (v3, w3)); + CHECK (isSameObject (v4, w4)); + } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0ce969fce..68c78bb5b 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -87263,8 +87263,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -87586,7 +87586,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -87612,9 +87612,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -87623,12 +87623,12 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -87655,9 +87655,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - + + + @@ -87758,7 +87758,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -88175,7 +88175,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -88232,8 +88232,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
heißt auch, man kann hier nicht mit SFINAE arbeiten — und exakt das hat sich am Ende auch als das Problem herausgestellt, die Compilation scheitert bevor unser Code überhaupt eingreifen kann

- - +
@@ -88321,8 +88320,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
 ⟹ Fehler im Test-Setup reproduzierbar

- - + @@ -88411,8 +88409,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
damit funktionieren die structured bindings

- - +
@@ -88429,16 +88426,16 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - + + @@ -88446,19 +88443,18 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - - - - - - - - + + + + + + + + + + @@ -142890,8 +142886,7 @@ std::cout << tmpl.render({"what", "World"}) << s es genügt daß die Member unter dem Namen direkt zugreifbar sind; sie müssen auch in der gleichen Struct liegen (die aber durchaus eine Basisklasse sein kann). Bit-Felder werden unterstützt, Union-Member jedoch nicht

- - +