From 10daa06ba20e51f882858d7a193b3943452f0f2d Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 6 Jun 2025 03:23:34 +0200 Subject: [PATCH] clean-up: simplify function-closure -- enable forwarding and remove workarounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a rather intricate and technical change, but allows in the end to switch back all usages to a main implementation patch, which is now based on `func::BindToArgument` — so this could become the final implementation core and replace the old `PApply` template eventually... Largely, these changes are related to allow for ''perfect forwarding'' of the functor and the argument values to be closed; these will be copied into the ''Binder object'' created by `std::bind`. Notably the `TupleConstructor` was changed to perfect-forward its »source« into the specialised `ElmMapper`; this is possible since the latter already receives a `SRC` template parameter, which can be supplied with whatever base type the `std::forward` invocation will expose. In the specialisation relevant here, template `PartiallyInitTuple`, now an ''universal reference'' is stored and passed to `std::get`, so that (depending on the input used), either a LValue or an RValue reference is used for the extracted data elements. After these changes, all existing usages of `applyFirst()` or `applyLast()` can be replaced by this modernised implementation back-end, thus obsoleting the various hard-coded workaround added during the last years. --- src/lib/meta/function-closure.hpp | 156 ++--- src/lib/meta/tuple-helper.hpp | 28 +- .../meta/function-composition-test.cpp | 59 +- tests/library/meta/tuple-closure-test.cpp | 2 +- wiki/thinkPad.ichthyo.mm | 581 ++++++++++++++++-- 5 files changed, 640 insertions(+), 186 deletions(-) diff --git a/src/lib/meta/function-closure.hpp b/src/lib/meta/function-closure.hpp index 51f04e1d2..94cb52e21 100644 --- a/src/lib/meta/function-closure.hpp +++ b/src/lib/meta/function-closure.hpp @@ -41,6 +41,7 @@ ** selected argument, which handles the intricacies of storing the functor. ** @todo 6/25 note however that the full-fledged partial function application is a ** **relevant core feature** and is used in the NodeBuilder (see tuple-closure.hpp). + ** Further improvement of the implementation is thus mandated... ////////////////////////////////////TICKET #1394 ** ** @see control::CommandDef usage example ** @see function-closure-test.hpp @@ -116,7 +117,7 @@ namespace func{ /** * Builder for a tuple instance, where only some ctor parameters are supplied, * while the remaining arguments will be default constructed. The use case is - * creating of a function binder, where some arguments shall be passed through + * to create a function binder, where some arguments shall be passed through * (and thus be stored in the resulting closure), while other arguments are just * marked as "Placeholder" with `std::_Placeholder`. * These placeholder marker terms just need to be default constructed, and will @@ -135,7 +136,7 @@ namespace func{ struct PartiallyInitTuple { template - using DestType = typename std::tuple_element::type; + using DestType = typename std::tuple_element_t; /** @@ -155,18 +156,18 @@ namespace func{ template struct IndexMapper { - SRC const& initArgs; + SRC&& initArgs; operator DestType() - { - return std::get (initArgs); + { // std::get passes-through reference kind + return std::get (forward(initArgs)); } }; template struct IndexMapper { - SRC const& initArgs; + SRC&& initArgs; operator DestType() { @@ -190,7 +191,7 @@ namespace func{ * @note both the functor and the arguments can be passed * by _perfect forwarding_, yet both will be **copied** * into the resulting binder - * @param fun anything »invocable« + * @param fun anything »invokable« * @param tuple a suitable set of arguments, or std binding placeholders * @return a _binder functor_, as generated by `std::bind`; all arguments * supplied with explicitly given values will be _closed_, while @@ -227,11 +228,13 @@ namespace func{ * @note the construction of this helper template does not verify or * match types to the signature. In case of mismatch, you'll get * a compilation failure from `std::bind` (which can be confusing) - * @todo 11/2023 started with modernising these functor utils. - * The most relevant bindFirst() / bindLast() operations do no longer - * rely on the PApply template. There is however the more general case - * of _binding multiple arguments,_ which is still used at a few places. - * Possibly PApply should be rewritten from scratch, using modern tooling. + * @todo 6/2025 while this is the core of this feature, the implementation is + * still confusing and bears traces from being the first attempt at solving + * this problem, with a pre-C++11 feature set. Driven by other requirements, + * over time several variations were added, so that now the main functionality + * is now _implemented twice_, in a very similar way in BindToArgument. + * Actually, the latter seems much clearer, and possibly PApply could be + * rewritten into a front-end and delegate to BindToArgument.. ////////////////////////////////////TICKET #1394 */ template class PApply @@ -338,6 +341,14 @@ namespace func{ /** * Bind a specific argument to an arbitrary value. * Notably this "value" might be another binder. + * + * @todo 6/2025 while this was added later to handle a special case, + * now after clean-up (see #987) this seems to be the much cleaner + * implementation approach and can easily be generalised to cover + * all the general features available through PApply; it would be + * sufficient to replace the single template parameter \a X by a + * type-sequence of the values to bind. All use cases of + * partial application could be modelled this way... //////////////////////////////////////////////TICKET #1394 */ template class BindToArgument @@ -374,13 +385,18 @@ namespace func{ public: - using ReducedFunc = function; + using ReducedFunc = function; ///////////////////////////////////////////////////////////TICKET #1394 : get rid of std::function ////OOO problem with tuple-closure + template static ReducedFunc - reduced (SIG& f, X val) + reduced (FUN&& f, VAL&& val) { - Tuple params {BuildPreparedArgs{std::make_tuple (val)}}; - return bindArgTuple (f, params); + Tuple bindingTuple { + BuildPreparedArgs{ + std::forward_as_tuple ( + forward(val))}}; + return bindArgTuple (forward(f) + , move(bindingTuple)); } }; @@ -421,68 +437,6 @@ namespace func{ } }; - template - struct _PapS - { - using Ret = typename _Fun::Ret; - using Args = typename _Fun::Args; - using Arg = typename Split::Head; - using Rest = typename Split::Tail; - - using FunType = typename BuildFunType::Fun; - static auto adaptedFunType() { return FunType{}; } - - - template - static auto - bindFrontArg (F&& fun, A&& arg, _Fun) - { - tuple binding{forward (fun) - ,forward (arg) - }; - return [binding = move(binding)] - (ARGS ...args) -> RET - { - auto& functor = get<0>(binding); - // //Warning: might corrupt ownership - return functor ( forward (unConst (get<1>(binding))) - , forward (args)...); - }; - } - }; - - template - struct _PapE - { - using Ret = typename _Fun::Ret; - using Args = typename _Fun::Args; - using Arg = typename Split::End; - using Rest = typename Split::Prefix; - - using FunType = typename BuildFunType::Fun; - static auto adaptedFunType() { return FunType{}; } - - - template - static auto - bindBackArg (F&& fun, A&& arg, _Fun) - { - tuple binding{forward (fun) - ,forward (arg) - }; - return [binding = move(binding)] - (ARGS ...args) -> RET - { - auto& functor = get<0>(binding); - // - return functor ( forward (args)... - , forward (unConst (get<1>(binding)))); - }; - } - }; - } // (End) argument type shortcuts @@ -490,44 +444,36 @@ namespace func{ /* ========== function-style interface ============= */ - /** close the given function over the first argument. - * @warning never tie an ownership-managing object by-value! */ - template + /** bind (close) the first function argument to an arbitrary term. + * @note this term may be a (nested) binder, which will + * be _activated late_, at the time of invocation + * @warning when an ownership-managing object is tied by-value, + * it will be moved and stored in the binder functor. + */ + template inline auto - applyFirst (FUN&& fun, ARG&& arg) + bindFirst (FUN&& f, TERM&& arg) { - static_assert (_Fun(), "expect something function-like"); - return _PapS::bindFrontArg (forward (fun) - ,forward (arg) - ,_PapS::adaptedFunType()); + return BindToArgument::reduced (std::forward (f) + ,std::forward (arg)); } - /** close the given function over the last argument */ - template + template inline auto - applyLast (FUN&& fun, ARG&& arg) + bindLast (FUN&& f, TERM&& arg) { - static_assert (_Fun(), "expect something function-like"); - return _PapE::bindBackArg (forward (fun) - ,forward (arg) - ,_PapE::adaptedFunType()); - } - - - /** bind the last function argument to an arbitrary term, - * which especially might be a (nested) binder... */ - template - inline - typename _PapE::FunType::Functor - bindLast (SIG& f, TERM&& arg) - { - enum { LAST_POS = -1 + count::Args>() }; - return BindToArgument::reduced (f, std::forward (arg)); + enum { LAST_POS = -1 + count::Args>() }; + return BindToArgument::reduced (std::forward (f) + ,std::forward (arg)); } /** build a functor chaining the given functions: feed the result of f1 into f2. * @note the mathematical notation would be `chained ≔ f2 ∘f1` + * @warning the »function« here are taken as _invokable_ and will be stored as-given + * into a tuple in the generated functor. If a _reference_ is passed in, + * then a reference is stored into the closure (beware!). This was + * added deliberately to support ove-only functors. */ template inline auto diff --git a/src/lib/meta/tuple-helper.hpp b/src/lib/meta/tuple-helper.hpp index 122c79133..4c0702281 100644 --- a/src/lib/meta/tuple-helper.hpp +++ b/src/lib/meta/tuple-helper.hpp @@ -191,17 +191,17 @@ namespace meta { - /** temporary workaround: match and rebind the type sequence from a tuple */ + /** match and rebind the type sequence from a tuple */ template struct RebindTupleTypes { - using Seq = typename TyOLD::Seq; + using Seq = typename TySeq::Seq; using List = typename Seq::List; }; template struct RebindTupleTypes> { - using Seq = typename TyOLD::Seq; + using Seq = typename TySeq::Seq; using List = typename Seq::List; }; @@ -242,17 +242,27 @@ namespace meta { /** meta-sequence to drive instantiation of the ElmMapper */ using SequenceIterator = typename BuildIdxIter::Ascending; + template + static auto + mapElm (SRC&& init) ///< initialise an instance of the element-mapper + { + return _ElmMapper_ + ,Tuple + , idx + >{std::forward (init)}; + } + protected: template - TupleConstructor (SRC initVals, IndexSeq) - : Tuple (_ElmMapper_, idx>{initVals}...) + TupleConstructor (SRC&& initVals, IndexSeq) + : Tuple {mapElm (std::forward(initVals)) ...} { } public: template - TupleConstructor (SRC values) - : TupleConstructor (std::move(values), SequenceIterator()) + TupleConstructor (SRC&& values) + : TupleConstructor (std::forward(values), SequenceIterator()) { } }; @@ -284,9 +294,9 @@ namespace meta { */ template Tuple - buildTuple (SRC values) + buildTuple (SRC&& values) { - return TupleConstructor (values); + return TupleConstructor{std::forward (values)}; } diff --git a/tests/library/meta/function-composition-test.cpp b/tests/library/meta/function-composition-test.cpp index 625df1d7a..1477bc424 100644 --- a/tests/library/meta/function-composition-test.cpp +++ b/tests/library/meta/function-composition-test.cpp @@ -32,8 +32,7 @@ namespace test { using ::test::Test; using lib::test::showType; using lib::meta::_Fun; - using func::applyFirst; - using func::applyLast; + using func::bindFirst; using func::bindLast; using func::PApply; using func::BindToArgument; @@ -236,7 +235,7 @@ namespace test { // Version4: as you'd typically do it in real life-------- // - fun_23 = func::applyFirst (f, Num<1>(18)); // use the convenience function API to close over a single value + fun_23 = func::bindFirst (f, Num<1>(18)); // use the convenience function API to close over a single value int r5 = fun_23(_2_,_3_).o_; // invoke the resulting functor... CHECK (23 == r5); @@ -245,36 +244,36 @@ namespace test { // what follows is the real unit test... function func123{f}; // alternatively do it with an std::function object - fun_23 = func::applyFirst (func123, Num<1>(19)); - int r5 = fun_23(_2_,_3_).o_; - CHECK (24 == r5); + fun_23 = func::bindFirst (func123, Num<1>(19)); + int r6 = fun_23(_2_,_3_).o_; + CHECK (24 == r6); using F12 = function(Num<1>, Num<2>)>; - F12 fun_12 = func::applyLast (f, Num<3>(20)); // close the *last* argument of a function - int r6 = fun_12(_1_,_2_).o_; - CHECK (23 == r6); - - fun_12 = func::applyLast (func123, Num<3>(21)); // alternatively use a function object + F12 fun_12 = func::bindLast (f, Num<3>(20)); // close the *last* argument of a function int r7 = fun_12(_1_,_2_).o_; - CHECK (24 == r7); + CHECK (23 == r7); + + fun_12 = func::bindLast (func123, Num<3>(21)); // alternatively use a function object + int r8 = fun_12(_1_,_2_).o_; + CHECK (24 == r8); Sig123* fP = &f; // a function pointer works too - fun_12 = func::applyLast (fP, Num<3>(22)); - int r8 = fun_12(_1_,_2_).o_; - CHECK (25 == r8); + fun_12 = func::bindLast (fP, Num<3>(22)); + int r9 = fun_12(_1_,_2_).o_; + CHECK (25 == r9); // cover more cases.... - CHECK (1 == (func::applyLast (fun11<1> , _1_ ) ( ) ).o_); - CHECK (1+3 == (func::applyLast (fun12<1,3> , _3_ ) (_1_) ).o_); - CHECK (1+3+5 == (func::applyLast (fun13<1,3,5> , _5_ ) (_1_,_3_) ).o_); - CHECK (1+3+5+7 == (func::applyLast (fun14<1,3,5,7> , _7_ ) (_1_,_3_,_5_) ).o_); - CHECK (1+3+5+7+9 == (func::applyLast (fun15<1,3,5,7,9>, _9_ ) (_1_,_3_,_5_,_7_)).o_); + CHECK (1 == (func::bindLast (fun11<1> , _1_ ) ( ) ).o_); + CHECK (1+3 == (func::bindLast (fun12<1,3> , _3_ ) (_1_) ).o_); + CHECK (1+3+5 == (func::bindLast (fun13<1,3,5> , _5_ ) (_1_,_3_) ).o_); + CHECK (1+3+5+7 == (func::bindLast (fun14<1,3,5,7> , _7_ ) (_1_,_3_,_5_) ).o_); + CHECK (1+3+5+7+9 == (func::bindLast (fun15<1,3,5,7,9>, _9_ ) (_1_,_3_,_5_,_7_)).o_); - CHECK (9+8+7+6+5 == (func::applyFirst(fun15<9,8,7,6,5>, _9_ ) (_8_,_7_,_6_,_5_)).o_); - CHECK ( 8+7+6+5 == (func::applyFirst( fun14<8,7,6,5>, _8_ ) (_7_,_6_,_5_)).o_); - CHECK ( 7+6+5 == (func::applyFirst( fun13<7,6,5>, _7_ ) (_6_,_5_)).o_); - CHECK ( 6+5 == (func::applyFirst( fun12<6,5>, _6_ ) (_5_)).o_); - CHECK ( 5 == (func::applyFirst( fun11<5>, _5_ ) ( )).o_); + CHECK (9+8+7+6+5 == (func::bindFirst(fun15<9,8,7,6,5>, _9_ ) (_8_,_7_,_6_,_5_)).o_); + CHECK ( 8+7+6+5 == (func::bindFirst( fun14<8,7,6,5>, _8_ ) (_7_,_6_,_5_)).o_); + CHECK ( 7+6+5 == (func::bindFirst( fun13<7,6,5>, _7_ ) (_6_,_5_)).o_); + CHECK ( 6+5 == (func::bindFirst( fun12<6,5>, _6_ ) (_5_)).o_); + CHECK ( 5 == (func::bindFirst( fun11<5>, _5_ ) ( )).o_); @@ -395,7 +394,7 @@ namespace test { // build chained and a partially applied functors auto chain = func::chained(f1,floorIt); - auto pappl = func::applyFirst (f1, ff); + auto pappl = func::bindFirst (f1, ff); using Sig1 = _Fun::Sig; using SigC = _Fun::Sig; @@ -416,14 +415,18 @@ namespace test { CHECK ( 97 == f1 (ff,ii,33)); CHECK ( 97 == chain(ff,ii,33)); - CHECK ( 97 == pappl( ii,33)); + + // NOTE: the partial-application generates a std::bind (Binder object), + // which deliberately _decays_ arguments to values. + CHECK (143 == pappl( ii,33)); // --> uses original *value* for f, but the int-ref (88+22+33) // can even exchange the actual function, since f1 was passed as reference fun = [](float& f, int& i, size_t s) -> double { return f - i - s; }; CHECK (-13 == f1 (ff,ii,33)); CHECK (-13 == chain(ff,ii,33)); - CHECK (-13 == pappl( ii,33)); + + CHECK (143 == pappl( ii,33)); // Note again: uses original value for the function and the float } }; diff --git a/tests/library/meta/tuple-closure-test.cpp b/tests/library/meta/tuple-closure-test.cpp index e512f4d6c..00d7f0243 100644 --- a/tests/library/meta/tuple-closure-test.cpp +++ b/tests/library/meta/tuple-closure-test.cpp @@ -159,7 +159,7 @@ namespace test { ArrayAdapt arr{1,2,3,4,5}; CHECK (arr.size() == 5); - // picks up a tuple-loke type signature + // picks up a tuple-like type signature using AA = decltype(arr); CHECK (showType() == "ArrayAdapt"_expect ); CHECK (showType() == "int"_expect ); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index f3636870b..92b887077 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -58872,7 +58872,7 @@ - + @@ -58945,8 +58945,9 @@ - + + @@ -59008,7 +59009,9 @@ - + + + @@ -59062,6 +59065,23 @@ + + + + +

+ Diese Anmerkung mache ich Juni 2025, als ich versucher herauszufinden, warum ich in den letzten zwei Jahren umgebaut habe, und warum ich den TupleConstructor auf const& gelassen habe, und einen eigenes Binder-λ implementiert habe. +

+
+

+ Es könnte nämlich sein, daß diese Bedenken rein theoretisch sind, weil std::bind explizit auf eine Kopie abstellt, also mit unique-ownership ohnehin nicht arbeiten könnte. Habe da wohl vor vier Monaten (Feb.25) explizit in die Implementierung der Stdlib geschaut.... +

+ +
+ + + +
@@ -59086,6 +59106,7 @@

+
@@ -59133,7 +59154,8 @@ - + + @@ -59151,7 +59173,8 @@ - + + @@ -106207,8 +106230,9 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + @@ -106487,7 +106511,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + @@ -106545,6 +106569,40 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) + + + + +

+ Anmerkung (function-closure aufgeräumt, Juni 25): +

+
    +
  • + habe jetzt in die alten Templates einen modernen Binder-Builder eingebaut +
  • +
  • + man kann jetzt Funktor und zu bindende Werte per Forwarding in den std::bind schieben +
  • +
  • + gebe jetzt eine Forwarding-Ref durch den TupleConstructor ! +
  • +
+ +
+ + + +

+ Das Ziel war, die ganzen Workarounds schrittweise zu reduzieren, damit letztlich alle Varianten wieder auf einen einzigen Implementierungspfad aufsetzen. Zwischenzeitlich waren nämlich bis zu drei verschiedene Implementierungslösungen aufgebaut worden. +

+

+ Für den TupleConstructor war die wichtige Einsicht, daß eine Forwarding-Referenz nicht zwangsläufig eine RValue-Referenz ist; vielmehr ist sie flexibel, weil sie auf einem Template-Argument aufsetzt, das an die direkte Aufruf-Quelle andockt. Dieses Muster läßt sich in den ElmMapper hineintragen, da dieser die Quelle als Parameter SRC bekommt. Wurde die Quelle also initial als LValue-Referenz in den TupleConstructor gegeben, dann wird dieser Parameter ebenfalls zur Referenz; andernfalls wird er zur RValue-Referenz. Damit, und mit dem explizit gegebenen Zieltyp für jedes Element im Ergebnis-Tupel läßt sich dieser ElmMapper also komplett »fernsteuern«. Ob das aber dennoch eine Falle darstellt für unique-values muß sich erst in der Praxis zeigen (wenn denn überhaupt jemals ein so komplexer ElmMapper gebraucht wird, der einzelne Elemente dupliziert, und diese Elemente dann aber unique-ownership hätten....) +

+ +
+ + +
@@ -133400,6 +133458,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension)

+ @@ -164360,17 +164419,21 @@ Since then others have made contributions, see the log for the history.
- +

- ...aber nur wenn's einfach geht; eigentlich ist das außerhalb vom Scope und könnte auch später mal gemacht werden (ist nur ein Implementierungsdetail), sofern die bestehende Impl mit den neuen Typlisten arbeitet + ...aber nur wenn's einfach geht; eigentlich ist das außerhalb vom Scope und könnte auch später mal gemacht werden, sofern nur die bestehende Impl mit den neuen Typlisten arbeitet. +

+
+

+ Tatsächlich hab ich das jetzt doch angehen müssen, da ich im Zuge der Umstellung in compile-Fehler gelaufen bin. Und das ist die Art Blocker, die ich für dieses Refactoring stets befürchtet habe; letztlich war es aber doch nicht so schlimm, denn bedingt durch den inkrementellen Ansatz konnte ich stückweise zurückgehen und hab dann gesehen, daß der Fehler „nur“ darauf zurückgeht, daß meine neue Ersatz-Implementierung nun voll generisch ist, und deshalb einen out-of-bounds-Zugriff nicht mehr stillschweigend abschneidet, sondern an std::bind durchreicht (welches dann die nicht passenden Argumenttypen bemerkt). Insgesamt war das nun etwa ein Tag full-time-Arbeit, und nun ist dieser bedrohliche Header wenigstens schon mal so weit reduziert, daß man die Redundanzen klar sehen kann

- - + + @@ -164397,7 +164460,7 @@ Since then others have made contributions, see the log for the history.
- + @@ -164409,7 +164472,8 @@ Since then others have made contributions, see the log for the history. - + + @@ -164421,9 +164485,7 @@ Since then others have made contributions, see the log for the history. - - - +

da es auf std::get<i> mit frest verdrahtetem Index aufsetzt @@ -164484,9 +164546,7 @@ Since then others have made contributions, see the log for the history. - - - +

d.h.: für die Back-Berchnung @@ -164530,9 +164590,7 @@ Since then others have made contributions, see the log for the history. - - - +

ECHT JETZT! @@ -164546,9 +164604,7 @@ Since then others have made contributions, see the log for the history. - - - +

da als rekursive Berechnung implementiert und der pos-Parameter auch korrekt als uint definiert @@ -164599,9 +164655,7 @@ Since then others have made contributions, see the log for the history. - - - +

Warum hab ich das nicht gleich gesehen????? @@ -164624,9 +164678,7 @@ Since then others have made contributions, see the log for the history. - - - +

also vermutlich war mir das damals so völlig klar, daß ich es einfach gemacht habe, ohne einen Kommentar zu hinterlassen; es war ja auch eine Spezialanfertigung für diesen einen Fall, und explizit für 1..9 Parameter so ausgeklopft @@ -164676,19 +164728,436 @@ Since then others have made contributions, see the log for the history. - - - - - + + + + + + + + + + + + + - + + + + + + + + + +

+ ...und zwar habe ich, mithilfe eines λ-generic, teilweise die Fähigkeit von std::bind nachgebaut, einen Functor per Forwarding in die Kopie zu nehmen; das war damals für mich ein ziemlicher Schritt vorwärts — wiewohl eigentlich auch die alte Implementierung auf std::bind aufbaut, und somit eigentlich dazu fähig sein müßte (wenn man sie denn druchdringen könnte....) +

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

+ wir greifen den aus dem Original-Tupel-Typ heraus; d.h. wenn std::get<i> eine Referenz liefert, wird diese an den Konstruktor dieses Zieltyps gegeben +

+ +
+ + + + +
    +
  • + wenn es sein Argument, also das Tupel normal bekommt, nimmt es eine Referenz und liefert auch eine Referenz auf das Einzelargument +
  • +
  • + wenn es dagegen sein Argument-Tupel per forwarding oder RValue bekommt, dann liefert es eine Forwarding-Referenz +
  • +
+ +
+ +
+
+
+ + + + + + + +

+ ....und auch der wurde noch nicht auf den Fall perfect-forwarding mit möglicherweise Referenzen im Tupel vorbereitet +

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

+ hier sollte alles komplett auf perfect forwarding bleiben; egal ob nun ein Einzel-Argument gegeben ist, oder (später mal) ein Tupel +

+ +
+
+ + + + + + + +

+ wenn in C++ ein Funktionsaufruf dazu dienen soll, eine Objektinstanz(Kopie) zu erstellen, dann sollte man nicht die Forwarding-Fälle durchreichen, sondern diese Handhabung besser dem Compiler überlassen, und die Kopie also sofort ganz vorne auf den Funktionsargumenten passieren lassen. Ab diesem Punkt kann man dieses Objekt per move ans Ziel schieben +

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

+ ...denn sie bekommt hier, was auf der Eingangs-Seite im Konstruktor gebunden wurde. Wurde also eingangsseitig ein perfect-forwarding gemacht, dann schwenkt dieser Parameter je nach gegebener Objekt-Art.  Man kann von sowas nun entweder kopieren, oder sich das selber als Forwarding-Referenz speichern +

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

+ commit 32b740cd400127b1d119b1e4bab490e063aa4f3e +

+

+ Author: Ichthyostega <prg@ichthyostega.de> +

+

+ Date:   Wed Nov 22 22:11:59 2023 +0100 +

+

+ +

+

+     Library: RandomDraw - dynamic configuration requires partial application +

+

+     +

+

+     Investigation in test setup reveals that the intended solution +

+

+     for dynamic configuration of the RandomDraw can not possibly work. +

+

+     The reason is: the processing function binds back into the object instance. +

+

+     This implies that RandomDraw must be *non-copyable*. +

+

+     +

+

+     So we have to go full circle. +

+

+     We need a way to pass the current instance to the configuration function. +

+

+     And the most obvious and clear way would be to pass it as function argument. +

+

+     Which however requires to *partially apply* this function. +

+

+     +

+

+     So -- again -- we have to resort to one of the functor utilities +

+

+     written several years ago; and while doing so, we must modernise +

+

+     these tools further, to support perfect forwarding and binding +

+

+     of reference arguments. +

+ +
+ +
+ + + + + +

+ in diesem Test wird +

+
    +
  • + einmal eine Referenz auf eine lokale Variable in die closure gespeichert +
  • +
  • + eine Referenz auf ein std::function-Object als Funktor gespeichert +
  • +
+

+ dann werden beide Referenzen manipuliert, und dadurch ändert sich (per Seiteneffekt) das Ergebnis, das man aus der den Funktoren für Funktions-Komposition und für partielle closure bekommt. Das heißt, die in diesem Testfall konstruierten Funktoren / Closures sind auf extreme Weise von verdecktem Kontext abhängig. +

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

+ Die neue Implementierung verwendet wieder die alte Template-Infrastruktur, aber gründlich modernisiert, mit variadics und std::apply. Damit können keine Referenzen in einen Binder gespeichert werden +

+ +
+
+
+ + + + + + +

+ Dabei ging es darum, einzelne Parameter in einem Tupel bereits »technisch« vorbinden zu können, während der Rest erst später von der Automation gesetzt wird. +

+

+ Beobachtung: das Thema Referenzen spielte in diesen Überlegungen keine Rolle. Vielmehr ging es um Performance: ich hatte Sorgen, weil das partial-application-Framework std::function-Objekte erzeugt; das bedeutet, daß jedes Lambda mit mehr als einem kontextuell gebundenen Wert in Heap-Storage wandert. Das  war die Hauptsorge hier +

+ +
+ +
+ + + + +

+ ...hatte damals aber keine Lösung gesehen, und war zu dem Schluß gekommen, daß dieser per Value arbeiten muß, weil sonst der ElmMapper heimtückischen Berschränkungen unterliegen könnte, wenn unique-ownership im Spiel ist. Weiß nicht, ob diese Sorge berechtigt ist. Jedenfalls ist mir damals der Unterschied zwischen RValue-Referenz und Universeller-Referenz(forwarding, template) nicht aufgefallen. +

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

+ Hier ist der Zusammenhang im Rückblick (auch nach längerer Recherche) nebulös. Ich habe mich damals wohl „verhackt“, d.h. ich stand unter Druck, weil das ganze ja nur ein Test-Hilfsmittel sein sollte (warum hat sich dann der ganze Scheduler-Stress-Test so extrem über mehrere Monate »gezogen«....?). Das RandomDraw, mit dieser Graph-Generierung ist wohl so eine Idee, die mich im Hintergrund fasziniert, und die eigentlich über das Ziel hinausschießt..... +

+

+ +

+

+ Wie dem auch sei, ich habe das durchgeprügelt, und dann bei den abschließenden Tests gemerkt, daß ich mit meinen Referenz-gebundenen Lambdas, auf die ich die ganze Lösung (für die Konfiguration dieses Zufalls-Generators) aufgebaut habe, ganz wunderbar in's eigene Bein geschossen habe: Das Ding soll ein Value-Objekt sein, darf aber nicht bewegt werden, sonst sind alle Referenzen in den Lambdas kaputt. Daraufhin habe ich mich mit einer noch wilderen Hack-Lösung gerettet...  (das LazyInit). Irgendwo in dieser Verzweiflungs-Aktion habe ich partielle Anwendung gebraucht; in der endgülitgen Lösung wird sie aber gar nicht mehr verwendet, sondern nur Funktions-Komposition (die Zufalls-Quelle wird dem Zieh-Mechanismus vorgeschaltet). Ich war damals (Winter 23/24) wieder sehr nah am low-level-Hacking, und hab extrem steilen Code geschrieben (über den ich mich jetzt, 1 Jahr später, schon wieder schäme. +

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

+ der ganz alte Code konnte noch keine Typ-Infrerenz verwenden und mußte deshalb stets einen Ergebnis-Typ definieren, der dann zwangsläufig eine std::function war. Das war einer der Hauptgründe, warum ich zwischenzeitlich mit diesem Framework in Probleme gelaufen bin, und deshalb angefangen habe, Teile „daneben auf der grünen Wiese“ zu bauen. (Erstaunlich, daß ich das alles nach 5 Montaten bereits restlos vergessen hatte) +

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

+ es geht darum, die alten nicht-variadischen (pre-C++11)-Konstrukte loszuwerden, bevor wir auf C++20 schalten. Es geht nicht darum, ein generells Funktion/Binding-Framework aufzubauen; das kann man schrittweise weiterentwickeln. Siehe das Ticket #1394 +

+ +
+ +
+ + + - + + + + + + + + +

+ ...und wird definitiv für den allgemeinsten Fall verwendet. Das Backend ist in beiden Fällen bindArgTuple() und damit std::bind. Und die Implementierung ist eigentlich gleich (da hab ich letztlich das Problem x-mal gelöst). +

+

+ +

+

+ Man könnte also BindToArgument so verallgemeinern, daß es eine typ-sequenz für mehrere Parameter nimmt, und die Werte dann als Tupel. Und dann könnte man ein function-style API als front-End davor stellen. +

+ +
+ +
@@ -164753,14 +165222,13 @@ Since then others have made contributions, see the log for the history.
- + - + + - - - +

Trigger... @@ -164785,6 +165253,33 @@ Since then others have made contributions, see the log for the history. + + + + +

+ lag vermutlich an dem λ-generic, +

+

+ welches inline definiert und per +

+

+ auto-Rückgabetyp geliefert wurde +

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