diff --git a/src/lib/meta/function-closure.hpp b/src/lib/meta/function-closure.hpp index 0c1764e6e..5c3f51701 100644 --- a/src/lib/meta/function-closure.hpp +++ b/src/lib/meta/function-closure.hpp @@ -83,6 +83,10 @@ namespace func{ * this Helper with repetitive specialisations for up to nine arguments * is used either to apply a function to arguments given as a tuple, or * to create the actual closure (functor) over all function arguments. + * @todo 2/2025 should be replaced by a single variadic template, and + * implemented using std::apply. Note also that std::bind would + * support perfect-forwarding, especially also for the functor; + * the latter would allow to use move-only functors. */ template struct Apply; @@ -567,11 +571,11 @@ namespace func{ template class PApply { - typedef typename _Fun::Args Args; - typedef typename _Fun::Ret Ret; - typedef typename Args::List ArgsList; - typedef typename VAL::List ValList; - typedef typename Types::Seq ValTypes; + using Args = typename _Fun::Args; + using Ret = typename _Fun::Ret; + using ArgsList = typename Args::List; + using ValList = typename VAL::List; + using ValTypes = typename Types::Seq; enum { ARG_CNT = count::value , VAL_CNT = count ::value @@ -580,24 +584,24 @@ namespace func{ // create list of the *remaining* arguments, after applying the ValList - typedef typename Splice::Back LeftReduced; - typedef typename Splice::Front RightReduced; + using LeftReduced = typename Splice::Back; + using RightReduced = typename Splice::Front; - typedef typename Types::Seq ArgsL; - typedef typename Types::Seq ArgsR; + using ArgsL = typename Types::Seq; + using ArgsR = typename Types::Seq; // build a list, where each of the *remaining* arguments is replaced by a placeholder marker - typedef typename func::PlaceholderTuple::List TrailingPlaceholders; - typedef typename func::PlaceholderTuple::List LeadingPlaceholders; + using TrailingPlaceholders = typename func::PlaceholderTuple::List; + using LeadingPlaceholders = typename func::PlaceholderTuple::List; // ... and splice these placeholders on top of the original argument type list, // thus retaining the types to be closed, but setting a placeholder for each remaining argument - typedef typename Splice::List LeftReplaced; - typedef typename Splice::List RightReplaced; + using LeftReplaced = typename Splice::List; + using RightReplaced = typename Splice::List; - typedef typename Types::Seq LeftReplacedTypes; - typedef typename Types::Seq RightReplacedTypes; + using LeftReplacedTypes = typename Types::Seq; + using RightReplacedTypes = typename Types::Seq; // create a "builder" helper, which accepts exactly the value tuple elements // and puts them at the right location, while default-constructing the remaining @@ -623,8 +627,8 @@ namespace func{ public: - typedef function::Sig> LeftReducedFunc; - typedef function::Sig> RightReducedFunc; + using LeftReducedFunc = function::Sig>; + using RightReducedFunc = function::Sig>; /** do a partial function application, closing the first arguments
@@ -634,11 +638,16 @@ namespace func{ * @param arg value tuple, used to close function arguments starting from left * @return new function object, holding copies of the values and using them at the * closed arguments; on invocation, only the remaining arguments need to be supplied. + * @note BuildL, i.e. the TupleApplicator _must take its arguments by-value._ Any attempt + * towards »perfect-forwarding« would be potentially fragile and not worth the effort, + * since the optimiser sees the operation as a whole. + * @todo 2/2025 However, the LeftReplacedArgs _could_ then possibly moved into the bind function, + * as could the functor, once we replace the Apply-template by STDLIB features. */ static LeftReducedFunc - bindFront (SIG const& f, Tuple const& arg) + bindFront (SIG const& f, Tuple arg) { - LeftReplacedArgs params {BuildL(arg)}; + LeftReplacedArgs params {BuildL(std::move(arg))}; return func::Apply::template bind (f, params); } @@ -651,9 +660,9 @@ namespace func{ * closed arguments; on invocation, only the remaining arguments need to be supplied. */ static RightReducedFunc - bindBack (SIG const& f, Tuple const& arg) + bindBack (SIG const& f, Tuple arg) { - RightReplacedArgs params {BuildR(arg)}; + RightReplacedArgs params {BuildR(std::move(arg))}; return func::Apply::template bind (f, params); } }; @@ -667,21 +676,23 @@ namespace func{ template class BindToArgument { - typedef typename _Fun::Args Args; - typedef typename _Fun::Ret Ret; - typedef typename Args::List ArgsList; - typedef typename Types::List ValList; + using Args = typename _Fun::Args; + using Ret = typename _Fun::Ret; + using ArgsList = typename Args::List; + using ValList = typename Types::List; enum { ARG_CNT = count::value }; - typedef typename Splice::Front RemainingFront; - typedef typename Splice::Back RemainingBack; - typedef typename func::PlaceholderTuple::List PlaceholdersBefore; - typedef typename func::PlaceholderTuple::List PlaceholdersBehind; - typedef typename Append< typename Append< PlaceholdersBefore - , ValList >::List - , PlaceholdersBehind >::List PreparedArgs; - typedef typename Append::List ReducedArgs; + using RemainingFront = typename Splice::Front; + using RemainingBack = typename Splice::Back; + using PlaceholdersBefore = typename func::PlaceholderTuple::List; + using PlaceholdersBehind = typename func::PlaceholderTuple::List; + + using PreparedArgs = typename Append< typename Append< PlaceholdersBefore + , ValList >::List + , PlaceholdersBehind + >::List; + using ReducedArgs = typename Append::List; using PreparedArgTypes = typename Types::Seq; using RemainingArgs = typename Types::Seq; @@ -696,12 +707,12 @@ namespace func{ public: - typedef function ReducedFunc; + using ReducedFunc = function; static ReducedFunc reduced (SIG& f, X val) { - Tuple params {BuildPreparedArgs{std::forward_as_tuple (val)}}; + Tuple params {BuildPreparedArgs{std::make_tuple (val)}}; return func::Apply::template bind (f, params); } }; diff --git a/src/lib/meta/tuple-closure.hpp b/src/lib/meta/tuple-closure.hpp index 3f28e210b..d6da31ad5 100644 --- a/src/lib/meta/tuple-closure.hpp +++ b/src/lib/meta/tuple-closure.hpp @@ -72,25 +72,28 @@ namespace meta{ template static auto - closeFront (VALS ...vs) + closeFront (VALS&& ...vs) { - using ClosedTypes = TySeq; - return wrapBuilder (func::PApply::bindFront (buildRecord, std::make_tuple(vs...))); + using ClosedTypes = TySeq...>; + auto boundArgs = std::make_tuple (std::forward (vs)...); // Note: must be passed by-val here + return wrapBuilder (func::PApply::bindFront (buildRecord, move(boundArgs))); } template static auto - closeBack (VALS ...vs) + closeBack (VALS&& ...vs) { - using ClosedTypes = TySeq; - return wrapBuilder (func::PApply::bindBack (buildRecord, std::make_tuple(vs...))); + using ClosedTypes = TySeq...>; + auto boundArgs = std::make_tuple (std::forward (vs)...); // Note: must be passed by-val here + return wrapBuilder (func::PApply::bindBack (buildRecord, move(boundArgs))); } template static auto - close (VAL val) + close (VAL&& val) { - return wrapBuilder (func::BindToArgument::reduced (buildRecord, val)); + using BoundVal = std::decay_t; + return wrapBuilder (func::BindToArgument::reduced (buildRecord, std::forward(val))); } private: diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 768b21ad6..2563d62e0 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -58819,7 +58819,7 @@ - + @@ -58971,6 +58971,68 @@ + + + + + + + +

+ Achtung: perfect-forwarding in das Tuple-Remapping hinein wäre gefährlich +

+ + +
+ + + +

+ Habe das im Detail analysiert. +

+
    +
  • + vor dem Binding wird ein remappetes Tupel konstruiert, bei dem u.U einzelne Werte durch Placeholder ersetzt wurden (und andere Werte an andere Positionen gingen) +
  • +
  • + dies baut auf TupleConstructor / ElmMapper auf. TupleConstructor nimmt das Tupel per-Value, ElmMapper reicht darauf eine Referenz +
  • +
  • + das ist essentiell, weil dadurch ElmMapper völlig frei ist und beliebige Mappings, auch mehrfach-Mappings realisieren kann +
  • +
  • + würde in dieses Vorstufen-Tupel eine RValue-Referenz gelangen, und würde man im ElmMapper eine RValue-Referenz durchreichen, dann würde jeder Wert sofort konsumiert. +
  • +
+ +
+
+ + + + + + + +

+ Denn: wenn man std::get das Quell-Tupel als RValue-Ref gibt, dann wird diese Funktion auch die Inhalte als RValue-Ref exponieren; im Zusammenspiel mit std::forward_as_tuple wäre so »perfect-forwarding« möglich, und für diesen zweiten Schritt auch sinnvoll, denn das remapped-Zwischen-Tupel bauen wir nur einmal auf, um es dann elementweise an den Binder weiterzugeben. Der Binder wiederum nimmt ausschließlich die zu bindenden Elemente (nicht die Placeholder) per RValue-Referenz und schiebt sie in ein Tuple in inline-Storage im Binder selber, wo sie dauerhaft liegen müssen, damit der Binder-Funktor beliebig oft aufgerufen werden kann +

+ +
+ +
+ + + + +

+ std::bind und std::apply schiebein alle Argumente per perfect-forwarding durch. Damit könnte man auch move-only-Funktoren verarbeiten, was die bestehende Impl nicht kann. +

+ +
+ +
+
@@ -105997,8 +106059,28 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - + + + + + + + + + + + + + +

+ d.h. man kann in der Praxis direkt mit einem Wert aufrufen, dann wird der eben implizit in ein 1-Tupel konvertiert +

+ + +
+ +
+
@@ -106024,16 +106106,15 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + + - + - - - +

wie steht's mit perfect forwarding? @@ -106045,9 +106126,7 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - - - +

ohnehin limitiert: wir konstruieren stets Werte in den Binder @@ -106062,22 +106141,70 @@ StM_bind(Builder<R1> b1, Extension<R1,R2> extension) - + - - - +

- ...denn nur auf diesem Weg hat der ElmMapper die komplette Freiheit und kann im Besonderen Werte selbst konstruieren, oder aber auch Werte mehrfach mappen + ...das bedeutet, der ElmMapper greift auf Inhalte zu, so wie sie im ctor-Argument des TupleConstructors liegen. Aber, da das Tupel selber per L-Value-Referenz eingebunden ist, wird auch std::get nur eine LValue-Referenz herausgeben (selbst wenn im Tupel selber eine RValue-Referenz liegen würde...). Das wäre nur anders, wenn man das ganze Tupel als RValue-Referenz an std::get gäbe...

+ +
+ + + + +

+ das ist hier sogar ein essentielles Feature +

+ +
+ + + +

+ ...denn nur auf diesem Weg hat der ElmMapper die komplette Freiheit und kann im Besonderen Werte selbst konstruieren, oder aber auch Werte mehrfach mappen. Denn wenn man das eingebundene Tupel als RValue an std::get geben würde, dann würde dieses eine RValue-Referenz von Value-Inhalten ziehen, und diese damit beim ersten Zugriff konsumieren. +

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