From a0d83b4fe83d8eeb20cbe0000a7858d9e725c2ab Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 7 Apr 2026 23:28:44 +0200 Subject: [PATCH] Library: investigate design for an opaque inline functor This is a design study to find out what would be required to build an opaque function wrapper, similar to `std::function`, yet without using any heap memory ever. Such a building block would allow for some interesting options for a well structured design of elaborate data structures. This changeset provides an implementation draft. __Two important findings to note__: * it is impossible to get away with just a single function pointer * because at the very least we need to invoke the destructor of the embedded delegate. Thus something akin to a "manager" is required. * this implies that the component would not be so efficient as I hoped, since it requires two pointers and requires to emit code for each type of delegate. * by making the component **NonCopyable**, I avoided the complexities of virtual copy support. In case the latter is desired, it would be much simpler to switch to a classical OO approach with a VTable and use our `PolymorphicValue` wrapper to handle copy and cloning. --- doc/technical/howto/crackNuts.txt | 10 + src/lib/meta/fun-inline.hpp | 237 ++++++++ src/test/diagnostic-output.hpp | 4 +- src/test/tracking-dummy.hpp | 34 +- tests/12metaprogramming.tests | 5 + tests/lib/branch-case-test.cpp | 16 +- tests/lib/linked-elements-test.cpp | 72 +-- tests/lib/meta/fun-inline-test.cpp | 164 ++++++ tests/lib/scoped-collection-test.cpp | 26 +- tests/lib/scoped-ptrvect-test.cpp | 26 +- tests/lib/several-builder-test.cpp | 32 +- tests/lib/thread-wrapper-autonomous-test.cpp | 2 +- tests/test/test-tracking-test.cpp | 28 +- tests/vault/gear/special-job-fun-test.cpp | 6 +- wiki/thinkPad.ichthyo.mm | 577 +++++++++++++++---- 15 files changed, 995 insertions(+), 244 deletions(-) create mode 100644 src/lib/meta/fun-inline.hpp create mode 100644 tests/lib/meta/fun-inline-test.cpp diff --git a/doc/technical/howto/crackNuts.txt b/doc/technical/howto/crackNuts.txt index d5741c03e..e66c488ac 100644 --- a/doc/technical/howto/crackNuts.txt +++ b/doc/technical/howto/crackNuts.txt @@ -235,4 +235,14 @@ unpack iterator into tuple:: - see tuple-record-init-test.cpp - used in 'command-simple-closure.hpp' to receive parameter values sent via UI-Bus and package them into a tuple for invocation of a Steam-Layer command. ++ +index and iterate over tuple elements:: + This is another example of bridging the compile-time / runtime barrier, using a trampoline. + Obviously such a solution requires that it is somehow possible to define a generic functor / λ that + can accept any of the tuple elements. And, in order to build a runtime index access, we additionally + need to convert each of the tuple elements into a _common reconciled type. ++ +- If such a type exists, can be figured out with the help of the `meta::CommonReturn` _metafunction_ +- `lib::meta::TupleIdxAdaptor` provides the necessary machinery to define a subscript `operator[]` +- see `TupleIdxAdaptor_test` for some usage examples, including a runtime iteration... diff --git a/src/lib/meta/fun-inline.hpp b/src/lib/meta/fun-inline.hpp new file mode 100644 index 000000000..bf6ad115c --- /dev/null +++ b/src/lib/meta/fun-inline.hpp @@ -0,0 +1,237 @@ +/* + FUN-INLINE.hpp - abstract functor with local delegate + + Copyright (C) + 2026 Hermann Vosseler + +  **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 fun-inline.hpp + ** A generic abstracted functor with the implementation stored locally. + ** This template is an implementation building block and allows to create + ** an abstraction barrier, similar to what std::function does. In contrast + ** to the latter however, FunInline places the actual implementation delegate + ** into an embedded opaque buffer, never using any heap memory. + ** + ** @todo WIP-WIP 4/2026 this is more like a conceptual draft, + ** not sure where this design exercise will lead to. + ** @warning after investigating the implementation technique to some degree, + ** it became clear that **copy support** is the greatest challenge. + ** All »the rest« might be surprisingly simple to implement, yet + ** the demand for a movable / copyable value-object effectively + ** translates into something like a VTable — and the result + ** would be similar to lib::PolymorphicValue — just with + ** some special binding to the function operator. + ** For this reason, FunInline is now declared to be util::NonCopyable + ** @todo 4/2026 I've finally got this draft hammered out. + ** It is far from what I wanted to achieve... + ** Notably a second function pointer is used + ** That is ugly, but it does the job. + ** + ** @see FunInline_test + ** + */ + + +#ifndef LIB_META_FUN_INLINE_H_ +#define LIB_META_FUN_INLINE_H_ + + +#include "lib/error.hpp" +#include "lib/nocopy.hpp" +#include "lib/meta/function.hpp" +#include "lib/meta/typeseq-util.hpp" +#include "lib/meta/variadic-helper.hpp" + +#include + + +namespace lib { +namespace meta{ + namespace err = lumiera::error; + + using std::forward; + + namespace test { class FunInline_test; } + + /** + * An opaque front-end for a functor stored inline. + * The type of the implementation delegate is erased + * from the type, retaining only the generic signature. + * @note this wrapper object is defined as NonCopyable, + * because anything else would require a VTable, + * and raise serious concerns regarding identity. + * @warning deliberately **not threadsafe** + * @see FunInline_test + */ + template + class FunInline + { + static_assert (!sizeof(SIG), "function signature required"); + }; + + + template + class FunInline + : util::NonCopyable + { + using _F = _Fun; + static_assert (_F{}, "function signature required"); + + using Sig = _F::Sig; + using Ret = _F::Ret; + using Args = _F::Args; + using RArgs = ElmTypes::template Apply; + using IArgs = Prepend::Seq; + using ISig = BuildFunType::Sig; + using Dtor = void(void*); + + /** virtualised delegation through function pointer(s) */ + ISig* invoker_{nullptr}; + Dtor* destroy_{nullptr}; + + /** opaque storage buffer to emplace the functor delegate */ + alignas(ALI) mutable + std::byte buf_[siz]; + + + /** + * The Invoker template is instantiated with the type of the concrete + * delegate functor (which resides in the opaque storage buffer). + * The purpose of this template is to cause generation of a static + * delegation function that can be invoked through the #invoker_ pointer. + * In the typical usage situation, the delegate type \a FUNC is implemented + * mostly with inline functions, and thus the bulk of the actual implementation + * code will be emitted into this static delegation function. The template + * \ref InvokerBuilder provides the blueprint for this code generation. + */ + template + struct Invoker + { + static Ret + dispatch (void* instance, ARGS&&...args) + { + return functor(instance) (forward (args)...); + } + + static void + destroy (void* instance) + { + if (instance) + functor(instance).~FUN(); + } + + static FUN& + functor (void* instance) + { + REQUIRE (instance); + return * static_cast (instance); + } + + using Sig = _F::Sig; + using ISig = decltype(dispatch); + }; + + /** allow the Unit test to poke into internals */ + friend class FunInline_test; + + + + public: + ~FunInline(){ destroy(); } + + FunInline() = default; + + template + FunInline (FUNC&& functor) + { + using Functor = std::decay_t; + emplace (forward (functor)); + activate(); + } + + // NonCopyable + + + operator bool() const + { + return bool(destroy_) and bool(invoker_); + } + + + /* == Functor interface == */ + + Ret + operator() (ARGS&&... args) + { + if (not invoker_) + throw err::State{"Attempt to invoke inactive function."}; + + return (*invoker_) (&buf_, forward (args)...); + } + + + private: /* == Instance management == */ + + void + destroy() + { + if (destroy_) + (*destroy_) (&buf_); + destroy_ = nullptr; + invoker_ = nullptr; + } + + template + void + activate() + { + ASSERT_VALID_SIGNATURE (FUNC, Sig); + REQUIRE (!destroy_ and !invoker_, "Lifecycle logic broken"); + + destroy_ = Invoker::destroy; + invoker_ = Invoker::dispatch; + } + + /** Abbreviation for placement new */ + template + TY& + emplace (INIT&& ...args) + { + static_assert (siz >= sizeof(TY), "Embedded storage buffer too small"); + static_assert (alignof(ALI) >= alignof(TY), "Alignment requirements not fulfilled"); + + destroy(); + try { + return *new(&buf_) TY {std::forward (args)...}; + } + catch (...) + { + ENSURE (not destroy_); + throw; + } + } + + /// @todo unused ... not required? + template + XXX& + accessAs() const + { + return * std::launder (reinterpret_cast (&buf_)); + } + }; + + +}}// namespace lib::meta +#endif /*LIB_META_FUN_INLINE_H_*/ diff --git a/src/test/diagnostic-output.hpp b/src/test/diagnostic-output.hpp index c7061ebcf..d595f7127 100644 --- a/src/test/diagnostic-output.hpp +++ b/src/test/diagnostic-output.hpp @@ -49,12 +49,12 @@ namespace test{ * Macro to print types and expressions to STDOUT, * using Lumiera's string conversion framework */ -#define SHOW_TYPE(_TY_) \ - cout << "typeof( " << STRINGIFY(_TY_) << " )= " << ::test::showType<_TY_>() <()" << " ? = " << ::test::showType<_TY_>() < 0); using BB = BranchCase; BB bb1{1, dummy}; - CHECK (bb1.get<1>().getVal() == rr); + CHECK (bb1.get<1>().getID() == rr); CHECK (2*rr == Dummy::checksum()); // got two instances due to copy-init BB bb2{0, "dummy"}; @@ -161,22 +161,22 @@ namespace test{ swap (bb1,bb2); CHECK (bb1.get<0>() == "dummy"); - CHECK (bb2.get<1>().getVal() == rr); + CHECK (bb2.get<1>().getID() == rr); CHECK (2*rr == Dummy::checksum()); bb1 = bb2; - CHECK (bb1.get<1>().getVal() == rr); + CHECK (bb1.get<1>().getID() == rr); CHECK (3*rr == Dummy::checksum()); // assignment by copy bb2 = move(bb1); // move-assignment CHECK (2*rr == Dummy::checksum()); // existing instance destroyed properly - CHECK (bb2.get<1>().getVal() == rr); - CHECK (bb1.get<1>().getVal() == Dummy::DEFUNCT); + CHECK (bb2.get<1>().getID() == rr); + CHECK (bb1.get<1>().getID() == Dummy::DEFUNCT); bb2 = BB{1,Dummy()}; // wipes out the other copy - auto rr2 = bb2.get<1>().getVal(); // but implants a different one + auto rr2 = bb2.get<1>().getID(); // but implants a different one CHECK (rr+rr2 == Dummy::checksum()); - CHECK (rr == dummy.getVal()); + CHECK (rr == dummy.getID()); }// leave scope: invoke dtors here CHECK (0 == Dummy::checksum()); diff --git a/tests/lib/linked-elements-test.cpp b/tests/lib/linked-elements-test.cpp index 23dcd5e56..1eec66770 100644 --- a/tests/lib/linked-elements-test.cpp +++ b/tests/lib/linked-elements-test.cpp @@ -113,7 +113,7 @@ namespace test{ virtual void nextResult(Pos& num) { - uint current = num->getVal(); + uint current = num->getID(); if (maxNum_ <= current) num = 0; else @@ -203,11 +203,11 @@ namespace test{ CHECK (5 == elements.size()); CHECK (0 != Dummy::checksum()); - CHECK (Dummy::checksum() == elements[0].getVal() - + elements[1].getVal() - + elements[2].getVal() - + elements[3].getVal() - + elements[4].getVal()); + CHECK (Dummy::checksum() == elements[0].getID() + + elements[1].getID() + + elements[2].getID() + + elements[3].getID() + + elements[4].getID()); elements.clear(); CHECK (isnil (elements)); @@ -240,7 +240,7 @@ namespace test{ List::iterator ii = elements.begin(); while (ii) { - CHECK (check == ii->getVal()); + CHECK (check == ii->getID()); CHECK (check == ii->calc(+5) - 5); --check; ++ii; @@ -254,7 +254,7 @@ namespace test{ List::const_iterator cii = const_elm.begin(); while (cii) { - CHECK (check == cii->getVal()); + CHECK (check == cii->getID()); --check; ++cii; } @@ -293,49 +293,49 @@ namespace test{ list.emplace(1); CHECK (not isnil (list)); - CHECK (1 == list[0].getVal()); + CHECK (1 == list[0].getID()); CHECK (1 == Dummy::checksum()); list.reverse(); CHECK (1 == Dummy::checksum()); - CHECK (1 == list[0].getVal()); + CHECK (1 == list[0].getID()); CHECK (not isnil (list)); list.emplace(2); CHECK (not isnil (list)); CHECK (2 == list.size()); - CHECK (2 == list[0].getVal()); + CHECK (2 == list[0].getID()); CHECK (2+1 == Dummy::checksum()); list.reverse(); CHECK (1+2 == Dummy::checksum()); - CHECK (1 == list[0].getVal()); + CHECK (1 == list[0].getID()); CHECK (2 == list.size()); list.emplace(3); CHECK (3 == list.size()); - CHECK (3 == list.top().getVal()); + CHECK (3 == list.top().getID()); CHECK (3+1+2 == Dummy::checksum()); list.reverse(); - CHECK (2 == list[0].getVal()); - CHECK (1 == list[1].getVal()); - CHECK (3 == list[2].getVal()); + CHECK (2 == list[0].getID()); + CHECK (1 == list[1].getID()); + CHECK (3 == list[2].getID()); List::iterator ii = list.begin(); - CHECK (2 == ii->getVal()); + CHECK (2 == ii->getID()); ++ii; - CHECK (1 == ii->getVal()); + CHECK (1 == ii->getID()); ++ii; - CHECK (3 == ii->getVal()); + CHECK (3 == ii->getID()); ++ii; CHECK (isnil (ii)); CHECK (2+1+3 == Dummy::checksum()); list.emplace(4); - CHECK (4 == list.top().getVal()); - CHECK (3 == list[3].getVal()); + CHECK (4 == list.top().getID()); + CHECK (3 == list[3].getID()); list.reverse(); - CHECK (3 == list[0].getVal()); - CHECK (1 == list[1].getVal()); - CHECK (2 == list[2].getVal()); - CHECK (4 == list[3].getVal()); + CHECK (3 == list[0].getID()); + CHECK (1 == list[1].getID()); + CHECK (2 == list[2].getID()); + CHECK (4 == list[3].getID()); CHECK (3+1+2+4 == Dummy::checksum()); } CHECK (0 == Dummy::checksum()); @@ -370,9 +370,9 @@ namespace test{ CHECK (3 == elements.size()); CHECK (22+44+66 == Dummy::checksum()); // not altered: we're referring the originals - CHECK (66 == elements[0].getVal()); - CHECK (44 == elements[1].getVal()); - CHECK (22 == elements[2].getVal()); + CHECK (66 == elements[0].getID()); + CHECK (44 == elements[1].getID()); + CHECK (22 == elements[2].getID()); CHECK (isSameObject(n2, elements[2])); CHECK (isSameObject(n4, elements[1])); CHECK (isSameObject(n6, elements[0])); @@ -403,8 +403,8 @@ namespace test{ CHECK (1+2 == Dummy::checksum()); CHECK (2 == elements.size()); - CHECK (2 == elements[0].getVal()); - CHECK (1 == elements[1].getVal()); + CHECK (2 == elements[0].getID()); + CHECK (1 == elements[1].getID()); elements.clear(); CHECK (0 == Dummy::checksum()); @@ -430,7 +430,7 @@ namespace test{ List::iterator ii = elements.begin(); while (ii) { - CHECK (check == ii->getVal()); + CHECK (check == ii->getID()); --check; ++ii; } @@ -513,9 +513,9 @@ namespace test{ CHECK (sum(9) == Dummy::checksum()); CHECK (3 == elements.size()); - CHECK (1+2 == elements[2].getVal()); - CHECK (3+4+5 == elements[1].getVal()); - CHECK (6+7+8+9 == elements[0].getVal()); + CHECK (1+2 == elements[2].getID()); + CHECK (3+4+5 == elements[1].getID()); + CHECK (6+7+8+9 == elements[0].getID()); elements.clear(); CHECK (EXPECT == cluster.numBytes()); @@ -540,8 +540,8 @@ namespace test{ CHECK (sum(5) == Dummy::checksum()); CHECK (2 == elements.size()); - CHECK (1+2 == elements[1].getVal()); - CHECK (3+4+5 == elements[0].getVal()); + CHECK (1+2 == elements[1].getID()); + CHECK (3+4+5 == elements[0].getID()); // note: this time the destructors will be invoked // from LinkedElements::clear(), but not from // the destructor of AllocationCluster diff --git a/tests/lib/meta/fun-inline-test.cpp b/tests/lib/meta/fun-inline-test.cpp new file mode 100644 index 000000000..fe69a837d --- /dev/null +++ b/tests/lib/meta/fun-inline-test.cpp @@ -0,0 +1,164 @@ +/* + FunInline(Test) - verify implementation of a functor with local delegate + + Copyright (C) + 2026, Hermann Vosseler + +  **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 fun-inline-test.cpp + ** unit test \ref FunInline_test + */ + + +#include "test/run.hpp" +#include "test/test-helper.hpp" +#include "test/tracking-dummy.hpp" +#include "lib/meta/fun-inline.hpp" +#include "test/diagnostic-output.hpp"///////////TODO jaleck + +using test::showType; +using test::Dummy; + + +namespace lib { +namespace meta { +namespace test { + + namespace { // test fixture... + + class TrackingFun + : public Dummy + { + public: + using Dummy::Dummy; + + long + operator() (int val) + { + return Dummy::calc(val); + } + }; + + }//(End) test fixture + + + + + + + + /*************************************************************//** + * @test Documentation of the defining structure for a functor + * that manages a delegate within an opaque inline buffer. + * - setup of the invocation delegation + * @remark this design complements std::function, insofar the + * wrapped functor implementation is stored within the + * FunInline itself, never using any heap memory. + * @todo 4/2026 unfinished design draft. + * @see FunctionSignature_test + */ + class FunInline_test : public Test + { + + virtual void + run (Arg) + { + seedRand(); + + simpleUsage(); + design_Invoker(); + design_BufferHandling(); + } + + + /** @test example usage of an functor wrapper with local storage. */ + void + simpleUsage() + { + TODO ("show simple usage of the front-end builder"); + } + + + /** @test document the design of the Invoker + */ + void + design_Invoker() + { + int tap{-1}; + auto funny = [&](int i){ return -(tap = i); }; + CHECK (-1 == tap); + CHECK (-2 == funny(2)); + CHECK ( 2 == tap); + + using Sig = long(int); + using FunIn = FunInline; + using Invoker = FunIn::Invoker; + + CHECK (showType() == "long (int)"_expect ); + CHECK (showType() == "long (void*, int&&)"_expect ); + + // The Invoker(Builder) actually defines a static function with that signature... + using InvoSig = _Fun::Sig; + CHECK (showType() == "long (void*, int&&)"_expect ); + + // and, through magic machinations of the C++ platform, + // this dispatcher function actually inlines the target functor... + InvoSig* invoker = Invoker::dispatch; + void* delegate = &funny; + + CHECK (-55 == (*invoker) (delegate, 55) ); + CHECK ( 55 == tap); + + // since this delegation through the invoker is an abstraction, + // other functors with compatible signature can be invoked as well, + // once a suitable Invoker was instantiated to generate dispatch code... + TrackingFun dummy; + invoker = FunIn::Invoker::dispatch; + delegate = &dummy; + + CHECK ((*invoker) (delegate, -55) == dummy.getID() - 55 ); + // Remark: Dummy::calc(arg) ≔ val + arg + } + + + /** @test verify proper instance handling + * for the embedded functor delegate + */ + void + design_BufferHandling() + { + using Sig = long(int); + using FunIn = FunInline; + + CHECK (0 == Dummy::checksum()); + { + TrackingFun dummy; + int id = dummy.getID(); + CHECK (id == Dummy::checksum()); + { + FunIn inFun{dummy}; + CHECK (inFun); + + CHECK (2*id == Dummy::checksum()); + + long res = inFun(42); + CHECK (res == id+42); + } + CHECK (id == Dummy::checksum()); + } + CHECK (0 == Dummy::checksum()); + } + }; + + + /** Register this test class... */ + LAUNCHER (FunInline_test, "unit common"); + + +}}}// namespace lib::meta::test diff --git a/tests/lib/scoped-collection-test.cpp b/tests/lib/scoped-collection-test.cpp index d30e5fb9f..2efade94b 100644 --- a/tests/lib/scoped-collection-test.cpp +++ b/tests/lib/scoped-collection-test.cpp @@ -47,7 +47,7 @@ namespace test{ calc (int i) { if (!i) - return getVal() + trigger_; + return getID() + trigger_; else return Dummy::calc(i); } @@ -57,7 +57,7 @@ namespace test{ : Dummy(id) , trigger_(trigger) { - if (trigger == getVal()) + if (trigger == getID()) throw error::Fatal ("Subversive Bomb", LUMIERA_ERROR_SUBVERSIVE); } @@ -130,11 +130,11 @@ namespace test{ CHECK (0 == Dummy::checksum()); container.populate(); - CHECK (Dummy::checksum() == container[0].getVal() - + container[1].getVal() - + container[2].getVal() - + container[3].getVal() - + container[4].getVal()); + CHECK (Dummy::checksum() == container[0].getID() + + container[1].getID() + + container[2].getID() + + container[3].getID() + + container[4].getID()); } CHECK (0 == Dummy::checksum()); } @@ -153,7 +153,7 @@ namespace test{ CollD::iterator ii = coll.begin(); while (ii) { - CHECK (check == ii->getVal()); + CHECK (check == ii->getID()); CHECK (check == ii->calc(+5) - 5); ++check; ++ii; @@ -166,7 +166,7 @@ namespace test{ CollD::const_iterator cii = const_coll.begin(); while (cii) { - CHECK (check == cii->getVal()); + CHECK (check == cii->getID()); ++check; ++cii; } @@ -176,13 +176,13 @@ namespace test{ check = 0; for (auto& entry : coll) { - CHECK (check == entry.getVal()); + CHECK (check == entry.getID()); ++check; } check = 0; for (auto const& entry : const_coll) { - CHECK (check == entry.getVal()); + CHECK (check == entry.getID()); ++check; } @@ -252,7 +252,7 @@ namespace test{ CHECK (sum + rr == Dummy::checksum()); - CHECK (d0.calc(11) == coll[0].getVal() + 11 ); + CHECK (d0.calc(11) == coll[0].getID() + 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 @@ -270,7 +270,7 @@ namespace test{ CHECK (d0.calc(0) == 11 + 22); // The others even point into obsoleted storage holding zombie objects - CHECK (d1.getVal() == Dummy::DEAD); + CHECK (d1.getID() == Dummy::DEAD); } CHECK (0 == Dummy::checksum()); diff --git a/tests/lib/scoped-ptrvect-test.cpp b/tests/lib/scoped-ptrvect-test.cpp index edb2381bd..56c2f2860 100644 --- a/tests/lib/scoped-ptrvect-test.cpp +++ b/tests/lib/scoped-ptrvect-test.cpp @@ -107,7 +107,7 @@ namespace test{ VectD::iterator ii = holder.begin(); while (ii) { - CHECK (check == ii->getVal()); + CHECK (check == ii->getID()); ++check; ++ii; } @@ -118,7 +118,7 @@ namespace test{ VectD::const_iterator cii = holder.begin(); while (cii) { - CHECK (check == cii->getVal()); + CHECK (check == cii->getID()); ++check; ++cii; } @@ -160,18 +160,18 @@ namespace test{ CHECK (5 == holder.size()); CHECK (0 < Dummy::checksum()); - id2 = holder[2].getVal(); - id3 = holder[3].getVal(); + id2 = holder[2].getID(); + id3 = holder[3].getID(); extracted = holder.detach(& holder[2]); - CHECK (id2 == extracted->getVal()); - CHECK (id3 == holder[2].getVal()); + CHECK (id2 == extracted->getID()); + CHECK (id3 == holder[2].getID()); CHECK (4 == holder.size()); } CHECK (0 < Dummy::checksum()); // not all dummies are dead CHECK (id2 == Dummy::checksum()); // #2 is alive! - extracted->setVal(id2+id3); + extracted->setID (id2+id3); CHECK (id2+id3 == Dummy::checksum()); delete extracted; @@ -194,9 +194,9 @@ namespace test{ CHECK ( isnil (left)); auto sum = Dummy::checksum(); CHECK (sum > 0); - int id0 = org[0].getVal(), - id1 = org[1].getVal(), - id2 = org[2].getVal(); + int id0 = org[0].getID(), + id1 = org[1].getID(), + id2 = org[2].getID(); // create by move VectD right{std::move (org)}; @@ -211,9 +211,9 @@ namespace test{ CHECK (not isnil (left)); CHECK ( isnil (right)); CHECK (sum == Dummy::checksum()); - CHECK (id0 == left[0].getVal()); - CHECK (id1 == left[1].getVal()); - CHECK (id2 == left[2].getVal()); + CHECK (id0 == left[0].getID()); + CHECK (id1 == left[1].getID()); + CHECK (id2 == left[2].getID()); } CHECK (0 == Dummy::checksum()); diff --git a/tests/lib/several-builder-test.cpp b/tests/lib/several-builder-test.cpp index d1fc71e4f..d897e6136 100644 --- a/tests/lib/several-builder-test.cpp +++ b/tests/lib/several-builder-test.cpp @@ -72,11 +72,11 @@ namespace test{ : Dummy(seed) { ext_.fill(seed); - setVal ((i+1)*seed); + setID ((i+1)*seed); } ~Num() { - setVal (getVal() - explore(ext_).resultSum()); + setID (getID() - explore(ext_).resultSum()); } long @@ -193,7 +193,7 @@ namespace test{ builder.emplace>() .emplace>(1); CHECK (2 == builder.size()); // use information functions... - CHECK (3 == builder[1].getVal()); // to peek into contents assembled thus far... + CHECK (3 == builder[1].getID()); // to peek into contents assembled thus far... VERIFY_ERROR (INDEX_BOUNDS, builder[2] ); // runtime bounds check on the builder (but not on the product!) builder.fillElm(2); CHECK (4 == builder.size()); @@ -204,16 +204,16 @@ namespace test{ CHECK ( isnil(builder)); CHECK (not isnil(elms)); CHECK (7 == elms.size()); - CHECK (elms[0].getVal() == (3+1)*3); // indeed a Num<3> with default-seed ≡ 3 - CHECK (elms[0].calc(1) == 3 + 1 + (3+3+3)); // indeed called the overridden calc() operation - CHECK (elms[1].getVal() == (2+1)*1); // indeed a Num<2> with seed ≡ 1 - CHECK (elms[1].calc(1) == 2 + 1 + (1+1)); // indeed the overridden calc() picking from the Array(1,1) - CHECK (isLimited (1, elms[2].getVal(), 100'000'000)); // indeed a Dummy with default random seed - CHECK (isLimited (1, elms[3].getVal(), 100'000'000)); // and this one too, since we filled in two instances - CHECK (elms[4].getVal() == 5); // followed by tree instances Dummy(5) - CHECK (elms[5].getVal() == 5); - CHECK (elms[6].getVal() == 5); - CHECK (elms[6].calc(1) == 5+1); // indeed invoking the base implementation of calc() + CHECK (elms[0].getID() == (3+1)*3); // indeed a Num<3> with default-seed ≡ 3 + CHECK (elms[0].calc(1) == 3 + 1 + (3+3+3)); // indeed called the overridden calc() operation + CHECK (elms[1].getID() == (2+1)*1); // indeed a Num<2> with seed ≡ 1 + CHECK (elms[1].calc(1) == 2 + 1 + (1+1)); // indeed the overridden calc() picking from the Array(1,1) + CHECK (isLimited (1, elms[2].getID(), 100'000'000)); // indeed a Dummy with default random seed + CHECK (isLimited (1, elms[3].getID(), 100'000'000)); // and this one too, since we filled in two instances + CHECK (elms[4].getID() == 5); // followed by tree instances Dummy(5) + CHECK (elms[5].getID() == 5); + CHECK (elms[6].getID() == 5); + CHECK (elms[6].calc(1) == 5+1); // indeed invoking the base implementation of calc() } { // Scenario-2 : unrelated element types @@ -656,11 +656,11 @@ namespace test{ elms = builder.build(); // Note: assigning to the existing front-end (which is storage agnostic) CHECK (5 == elms.size()); - CHECK (23 == elms.back().getVal()); - CHECK (55 == extraDummy.getVal()); + CHECK (23 == elms.back().getID()); + CHECK (55 == extraDummy.getID()); } // Now the Builder and the ExtraDummy is gone... CHECK (5 == elms.size()); // while all created elements are still there, sitting in the Allocationcluster - CHECK (23 == elms.back().getVal()); + CHECK (23 == elms.back().getID()); CHECK (2 == clu.numExtents()); CHECK (clu.numBytes() == allotted); diff --git a/tests/lib/thread-wrapper-autonomous-test.cpp b/tests/lib/thread-wrapper-autonomous-test.cpp index 45fd880eb..8e87d3730 100644 --- a/tests/lib/thread-wrapper-autonomous-test.cpp +++ b/tests/lib/thread-wrapper-autonomous-test.cpp @@ -79,7 +79,7 @@ namespace test{ void doIt (int extra) { - watcher.setVal (extra); + watcher.setID (extra); sleep_for (5ms); } }; diff --git a/tests/test/test-tracking-test.cpp b/tests/test/test-tracking-test.cpp index db075a18e..860e4a3be 100644 --- a/tests/test/test-tracking-test.cpp +++ b/tests/test/test-tracking-test.cpp @@ -117,24 +117,24 @@ namespace test{ CHECK (Dummy::checksum() == 0); { Dummy dum1; // picks a random positive int by default... - CHECK (0 < dum1.getVal() and dum1.getVal() <= 100'000'000); - CHECK (Dummy::checksum() == dum1.getVal()); + CHECK (0 < dum1.getID() and dum1.getID() <= 100'000'000); + CHECK (Dummy::checksum() == dum1.getID()); Dummy dum2{55}; - CHECK (dum2.getVal() == 55); - CHECK (Dummy::checksum() == dum1.getVal() + 55); + CHECK (dum2.getID() == 55); + CHECK (Dummy::checksum() == dum1.getID() + 55); Dummy dum3{move (dum2)}; - CHECK (dum3.getVal() == 55); - CHECK (dum2.getVal() == Dummy::DEFUNCT); + CHECK (dum3.getID() == 55); + CHECK (dum2.getID() == Dummy::DEFUNCT); - dum3.setVal (23); - CHECK (dum3.getVal() == 23); + dum3.setID (23); + CHECK (dum3.getID() == 23); dum1 = move (dum3); - CHECK (dum1.getVal() == 23 ); - CHECK (dum2.getVal() == Dummy::DEFUNCT); - CHECK (dum3.getVal() == Dummy::DEFUNCT); + CHECK (dum1.getID() == 23 ); + CHECK (dum2.getID() == Dummy::DEFUNCT); + CHECK (dum3.getID() == Dummy::DEFUNCT); CHECK (Dummy::checksum() == 23 ); Dummy::activateCtorFailure (true); @@ -148,9 +148,9 @@ namespace test{ Dummy::checksum() -= v; } Dummy::activateCtorFailure (false); - CHECK (dum1.getVal() == 23 ); - CHECK (dum2.getVal() == Dummy::DEFUNCT); - CHECK (dum3.getVal() == Dummy::DEFUNCT); + CHECK (dum1.getID() == 23 ); + CHECK (dum2.getID() == Dummy::DEFUNCT); + CHECK (dum3.getID() == Dummy::DEFUNCT); CHECK (Dummy::checksum() == 23 ); } CHECK (Dummy::checksum() == 0); diff --git a/tests/vault/gear/special-job-fun-test.cpp b/tests/vault/gear/special-job-fun-test.cpp index 6668ecc6f..0d33e527c 100644 --- a/tests/vault/gear/special-job-fun-test.cpp +++ b/tests/vault/gear/special-job-fun-test.cpp @@ -99,7 +99,7 @@ namespace test { * - verify the front-end can be copied without impact on the JobFunctor * - verify the heap allocated functor keeps itself alive * even when the front-end handle is already gone. - * - verify the functor de-allocates itself after latst invocation + * - verify the functor de-allocates itself after last invocation * @todo WIP 12/23 ✔ define ⟶ ✔ implement */ void @@ -111,7 +111,7 @@ namespace test { (JobParameter param) mutable { int mark = param.invoKey.part.a; - tracker.setVal (mark); + tracker.setID (mark); }}; // △△△ invocation should alter checksum // one Dummy instance was implanted @@ -134,7 +134,7 @@ namespace test { (JobParameter param) mutable { int mark = param.invoKey.part.a; - tracker.setVal (mark); + tracker.setID (mark); }; CHECK (23 == Dummy::checksum()); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 47d205731..d7ed6c7bb 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -52318,7 +52318,9 @@ - + + + @@ -54572,7 +54574,7 @@ - + @@ -54606,10 +54608,10 @@ - + - + @@ -54642,7 +54644,7 @@ - + @@ -54670,7 +54672,7 @@ - + @@ -54809,6 +54811,7 @@ + @@ -55037,7 +55040,7 @@ - + @@ -55052,6 +55055,12 @@ + + + + + + @@ -55254,6 +55263,7 @@

+
@@ -61280,8 +61290,9 @@
- - + + + @@ -61299,7 +61310,7 @@ - + @@ -61334,7 +61345,7 @@ - + @@ -61355,7 +61366,7 @@ - + @@ -61405,7 +61416,7 @@ - + @@ -61505,7 +61516,7 @@ - + @@ -61528,8 +61539,7 @@ ...ist inzwischen nicht einmal mehr klar, ob das Bug oder Feature ist; jedenfalls ist lib::meta::typeStr so geschrieben, daß es const, * und & absorbiert. Und typeStr wird an hundertausend Stellen verwendet...

- - +
@@ -61540,8 +61550,7 @@ Vom Umfang der Features her ist das möglich, und das eine spezialisierte Template werden wir uns wohl noch leisten können (im Zweifelsfall gibt es durchaus andere Sachen, die könnten aus meta/Util in die Traits)

- - +
@@ -61552,8 +61561,7 @@ Erst dachte ich meta/util.hpp, aber da gibt es kein lib::Literal, und außerdem habe ich dann doch etwas Bauchweh mit dem großen Template. Demgegenüber erscheint format-object.hpp der natürliche Platz, da sind auch toString() und typedString() und demangleCxx()

- - +
@@ -61565,7 +61573,7 @@
- + @@ -61613,8 +61621,7 @@ Da std::common_type automatisch den decay_t anwendet, dachte ich damals, ich kann die Referenzen und die Const-ness einfach nachträglich fixen. Das führt aber in gewissen Randfällen zu einem gefährlich falschen Ergebnis

- - +
@@ -61625,14 +61632,14 @@ die STDLIB macht ein »decay« (für common_type) bzw. hat elaborierte Logik, um mit RValue-Referenzen umzugehen.

- - +
- + + @@ -61714,7 +61721,7 @@ - + @@ -61764,7 +61771,7 @@ - + @@ -61784,8 +61791,7 @@ ...nämlich auf dem Level von IterExplorer. Dort sind auch bereits die Function-Tools  da, und damit implizit auch schon Typelist und Typeseq....

- -
+
@@ -61796,8 +61802,7 @@ So eine Spezialisierung, die in irgend einem Basis-Header versteckt ist, empfinde ich immer als hochproblematisch; man verwendet sie meist, ohne zu realisieren, daß sie existiert, und man findet sie im Zweifelsfall nur schwer, besonders wenn das Feature viel verwendet wird

- - +
@@ -61807,8 +61812,7 @@ Es bildet sich nämlich immer mehr ein eigener Status heraus, auf dem erst die Typlisten- und Sequenz-Manipulationen stattfinden. Dieser wird seit etwa einem Jahr durch das ElmTypes-Template definiert. Und die Tuple-Metafunktionen liegen noch eine Ebene höher

- -
+
@@ -61818,8 +61822,7 @@ Was ich allerdings auch dann wieder gut finde, denn ich benenne die Namespaces dann lieber inhaltlich, denn sie sollen ja auch dann noch für eine gewisse Trennung sorgen, wenn alle Ebenen der Library eingeschlossen sind

- -
+
@@ -61836,8 +61839,7 @@ ...damit das Feature als Solches in diesem Kontext gezeigt wird, denn inhaltlich  wird es oft relevant, wenn man aus einer StateCore heraus Werte aus verschiedenen Quellen kombiniert

- - +
@@ -61858,7 +61860,7 @@
- + @@ -61881,8 +61883,7 @@ Grundsätzlich soll meine Metafunktion den Basismechanismus  exponieren

- - +
@@ -61920,8 +61921,7 @@ - - + @@ -61993,8 +61993,7 @@ Und lib::Several definiert seinen value_type und reference per RefTraits. Also kommt hier eine Referenz auf einen std::ReferenceWrapper<ProcNode> raus. Welcher selber wiederum konvertierbar ist auf ProcNode& ... Und dieses Setup wird dann von den Type-Traits in IterExplorer analysiert.

- - + @@ -62016,8 +62015,7 @@

- -
+
@@ -62033,8 +62031,7 @@ aka 'struct lib::meta::CommonResult<std::reference_wrapper<steam::engine::ProcNode>&, std::reference_wrapper<steam::engine::ProcNode>&>'

- - +
@@ -62043,7 +62040,7 @@ - + @@ -62051,8 +62048,7 @@ da die Lösung mit std::declval<T>() ja funktioniert ⟹ Problem vertagen

- -
+ @@ -62065,11 +62061,12 @@ - + + - + @@ -62096,7 +62093,7 @@ - + @@ -62133,7 +62130,7 @@ - + @@ -62142,7 +62139,7 @@ - + @@ -62193,7 +62190,7 @@ - + @@ -62245,7 +62242,7 @@ - + @@ -62341,7 +62338,7 @@ - + @@ -62436,7 +62433,7 @@ - + @@ -62455,7 +62452,7 @@ - + @@ -62491,6 +62488,9 @@ + + + @@ -62501,7 +62501,7 @@ - + @@ -62524,8 +62524,7 @@ using TupIt = lib::IterableDecorator<lib::IdxStoreCore<IdxApt>>;

- -
+
@@ -62542,8 +62541,8 @@ - - + + @@ -62566,13 +62565,15 @@
- + - - + + + + @@ -62582,8 +62583,7 @@ das Quell-Objekt liegt im Iterator

- -
+ @@ -62591,8 +62591,7 @@ Meine vorausgegangenen Untersuchungen zur generischen Implementierung einer flatten()-Operation hat mir gezeigt, daß man sich damit in einer gefährlichen Grenzzone bewegt — sobald diese eingebette Quelle irgendwohin kopiert wird, kann die Logik brechen

- -
+
@@ -62606,8 +62605,7 @@ ...implementiere den default-Ctor explizit und setze i_ ≔ size

- - +
@@ -62621,8 +62619,7 @@ ...weil das auf einer Kopie des Iterators arbeitet, die durch den Aufruf von begin(it) erzeugt wird — immerhin funktioniert das Iterations-Ende nun, nachdem ich den Kniff für den default-konstruierten IdxStoreCore angewendet habe

- - +
@@ -62646,8 +62643,9 @@ - + + @@ -62701,7 +62699,9 @@ - + + + @@ -62719,8 +62719,7 @@ Immerhin habe ich ja jetzt ein Concept tuple_like — und der reine Check auf ein Concept hat bisher stets völlig problemlos funktioniert...

- - +
@@ -62739,8 +62738,7 @@ das ist eine Typ-Alias, die direkt per Typlist / Typsequenz gebaut werden kann; es ist tatsächlich ein std::tuple

- - +
@@ -62761,8 +62759,7 @@ das ist also ein Experiment: werde ich mich daran erinnern können, oder doch immer ElmTypes nehmen?

- - + @@ -62779,8 +62776,7 @@ hat zwei signifikante Verwendungen

- - + @@ -62801,8 +62797,7 @@ man könnte eine neue Implementierung machen, die dann ein generisches Lambda nimmt; dieses hätte aber eine wesentlich implizitere Schnittstelle und müßte die Typen von seinen Argumenten abgreifen, bzw. man müßte auch diesem Lambda ein explizites Typ-Argument geben — das bedeutet, die Labda-basierte Lösung wäre hier verwirrender und nicht unbedingt eine Verbesserung

- - +
@@ -62839,8 +62834,7 @@ - - + @@ -62872,8 +62866,7 @@ baut einen Dekorator-Chain über das Tupel als Basistyp

- - +
@@ -62888,8 +62881,7 @@ ...wie so oft in diesen ersten Jahren; ich hab über Wochen oder gar Monate diverse Frameworks gebaut, nur um dann — schließlich, endlich — festzustellen, daß ich mir nicht im Entferntesten vorstellen kann, wie ich mit diesem Framework das weiterhin ungreifbare Problem zu fassen bekomme.

- - +
@@ -62901,8 +62893,8 @@
- - + + @@ -62912,8 +62904,7 @@ Da war das der FunctionApplicator / die FunctionClosure, die an dieser Stelle erzeugt wurde.

- -
+
@@ -62931,7 +62922,7 @@
- + @@ -62965,7 +62956,7 @@ - + @@ -62990,7 +62981,7 @@ - + @@ -63035,8 +63026,7 @@ einen variadischen Overload von join bereitzustellen

- - + @@ -63044,8 +63034,7 @@ ...was letztlich nur zu endlosen compile-Problemen in speziellen Randfällen führte — und schließlich zu der Einsicht, daß es ein gänzlich anderes Tool ist, und auch entsprechend anders heißen sollte; seitdem heißt es stringify() und liefert einen Container mit Strings

- -
+
@@ -63069,12 +63058,11 @@ eigentlich fällt hier nur eine compile-time Sequenz von string-Werten an

- - + - + @@ -63093,8 +63081,7 @@ - - + @@ -63102,8 +63089,8 @@ - - + + @@ -63142,14 +63129,20 @@ was ich jetzt nicht mache...

- - + + + + + + + +
@@ -63361,7 +63354,7 @@
- + @@ -63385,7 +63378,7 @@ - + @@ -63439,6 +63432,314 @@ + + + + +

+ Das ergibt sich direkt aus der beschriebenen Äquivalenz: sobald es nämlich mehrere Methoden gibt, müssen auch mehrere Funktionspointer irgendwo gespeichert werden. Insoweit wären beide Ansätze noch äquivalent. Aber die Implementierung eines virtuellen Iterfaces bedingt praktisch immer auch einen gemeinsamen Implementierungs-Scope, also ein Objekt mit gemeinsamen Daten. Und um aus mehreren Funktionen darauf zuzugreifen brauchen wir in jeder Funktion einen Funktor, der über einen weiteren Pointer an das gemeinsame Implementierungs-Objekt delegiert. Demgegenüber ist das Setup einers virtuellen Interfaces durchaus effizient.... +

+ +
+
+ + + + +

+ ...als der Aufruf eines virtuellen Interfaces über eine Referenz-auf-das-Interface. Dieser Aufruf durch die Referenz stellt eine weitere Indirektion dar, und damit ein weiteres Risiko für Cache-miss. Dagegen bei einem »polymorphic value« kann das nicht passieren, denn dort ist das Implementierungs-Objekt bereits lokal da, wenngleich auch in einem opaque-buffer. Für den Aufruf wird nur der Zugang zur VTable benötigt, und das ist eine lokal sichtbare Funktion (wenn der Compiler/Optimiser nicht komplett dumm ist). Danach wird durch die Funktion in der VTable ein "this"-Pointer durchgereicht, welcher bereits in lokal vorhandene Storage zeigt. +

+ +
+ +
+ + + + +

+ Denn das ist eine doppelte-Indirektion; das kann nur gerechtfertigt sein, wenn es sich auch um einen double-dispatch handelt +

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

+ Weil ich das Thema immer schon interessant fand, und mich jetzt grundsätzlich mit den Zusammenhängen vertraut gemacht habe, da ich verschiedene Design-Varianten für den OutputSlot + BufferProvider durchdacht habe. Ich sehe jetzt eine erste Basis-Implementierung bereits »vor meinem geistigen Auge«. Und ich weiß, daß ich diese Einsicht wieder verlieren werde — sowas kann man nicht aufschreiben (außer in Code). Zur weiteren Rechtfertigung (ja ich fühle mich schuldig) möchte ich hinzufügen, daß ein neues Design erfahrungsgemäß viel Zeit und mehrere Anläufe zur Reife braucht. +

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

+ also eigentlich eine VTable — oder gleich ein »polymorphic value« +

+ +
+ +
+ + + + +

+ std::function (Lib StdC++) verwendet dafür einen separaten Manager* +

+ +
+ + + + + + + + +

+ Weiterer Einwand: Kopie des eingebetteten Funktors ⟶ was passiert dann mit dem Zustand? +

+ +
+ + + + + + + +

+ ...das heißt, man erwartet eigentlich, daß eine "function" eine eindeutige Identität hat, und ich das front-End-Handle beliebig herumschieben kann; ob dem tatsächlich so ist, hängt aber an Feinheiten der Implementierung +

+ +
+ +
+
+
+ + + + +

+ Dieses Experiment erst mal als NonCopyable fertigstellen +

+ +
+ + + + + +

+ ...denn (abgesehen von dem virtual copy support) wäre die sonstige Aufgabe im Grunde verblüffend einfach +

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

+ ...weil es sich um einen Container handelt, über dessen Inhalt wir nichst wissen(wollen). Deshalb kann es sein, daß ein Objekt im Container eine Identität hat. Und ein Objekt mit Identität hat immer einen Lebenszyklus +

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

+ dann die wrapped-pointer-Variante instantiieren +

+ +
+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -172050,7 +172351,8 @@ std::cout << tmpl.render({"what", "World"}) << s - + + @@ -172298,6 +172600,9 @@ std::cout << tmpl.render({"what", "World"}) << s + + + @@ -172332,7 +172637,37 @@ std::cout << tmpl.render({"what", "World"}) << s - + + + + + + + + + + +

+ nimmt zuzuweisendes Argument by-value +

+ +
+ + + +

+ das ist der Kniff!
Der Compiler kann das bestens optimieren; wenn eine Referenz zugewiesen wird, geht das durch den copy-ctor, sonst durch den move-ctor ⟹ man implementiert diese Logik nur ein einziges mal +

+ +
+
+ + + +
+
+
+ @@ -207801,7 +208136,7 @@ env.Command('out.bin', 'in.bin', my_action) - +