/* FUNCTION-CLOSURE.hpp - metaprogramming tools for closing a function over given arguments Copyright (C) 2009, 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 function-closure.hpp ** Partial function application and building a complete function closure. ** These are features known from functional programming and are here implemented ** based on std::bind, to support especially the case when some arguments of a ** function are known at an earlier stage, and should thus be »baked in«, while ** further arguments will be supplied later, for the actual invocation. This ** implies to _close_ (and thus bind) some arguments immediately, while keeping ** other arguments open to — so the result of this operation is again a function, ** albeit with fewer arguments. ** Additionally, we allow for _composing_ (chaining) of two functions. ** @warning this header is in a state of transition as of 6/2025, because functionality ** of this kind will certainly be needed in future, but with full support for lambdas, ** move-only types and perfect forwarding. The current implementation of std::bind ** is already quite optimal regarding this support. A gradual rework has been started, ** and will lead to a complete rewrite of the core functionality eventually, making ** better use of variadic templates and library functions like std::apply, which ** were not available at the time of the first implementation. ** ** @todo the implementation is able to handle partial application with N arguments, ** but currently we need just one argument, thus only this case was wrapped ** up into a convenient functions func::applyFirst and func::applyLast ** @todo 11/23 these functor-utils were written at a time when support for handling ** generic functions in C++ was woefully inadequate; at that time, we neither ** had Lambda-support in the language, nor the ability to use variadic arguments. ** Providing a one-shot function-style interface for this kind of manipulations ** is still considered beneficial, and thus we should gradually modernise ** the tools we want to retain... ** @todo 2/25 note that there is a bind_front in C++20 and C++23 will provide a bind_back ** helper, which would provide the implementation fully in accordance with current ** expectations (including move, constexpr); if we choose to retain a generic ** function-style front-end, it should be aligned with these standard facilities. ** We might want to retain a simple generic interface especially for binding some ** 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). ** ** @see control::CommandDef usage example ** @see function-closure-test.hpp ** @see function-composition-test.hpp ** @see function.hpp ** */ #ifndef LIB_META_FUNCTION_CLOSURE_H #define LIB_META_FUNCTION_CLOSURE_H #include "lib/meta/function.hpp" #include "lib/meta/tuple-helper.hpp" #include "lib/util.hpp" #include #include #include namespace lib { namespace meta{ namespace func{ using std::tuple; using std::function; using std::forward; using std::move; namespace { // helpers for binding and applying a function to an argument tuple using std::get; /** * 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; template<> //__________________________________ struct Apply<0> ///< Apply function without Arguments { template static RET invoke (FUN& f, TUP&) { return f (); } template static RET bind (FUN& f, TUP&) { return std::bind (f); } }; template<> //_________________________________ struct Apply<1> ///< Apply function with 1 Argument { template static RET invoke (FUN& f, TUP & arg) { return f (get<0>(arg)); } template static RET bind (FUN& f, TUP & arg) { return std::bind (f, get<0>(arg)); } }; template<> //_________________________________ struct Apply<2> ///< Apply function with 2 Arguments { template static RET invoke (FUN& f, TUP & arg) { return f ( get<0>(arg) , get<1>(arg) ); } template static RET bind (FUN& f, TUP & arg) { return std::bind (f, get<0>(arg) , get<1>(arg) ); } }; template<> //_________________________________ struct Apply<3> ///< Apply function with 3 Arguments { template static RET invoke (FUN& f, TUP & arg) { return f ( get<0>(arg) , get<1>(arg) , get<2>(arg) ); } template static RET bind (FUN& f, TUP & arg) { return std::bind (f, get<0>(arg) , get<1>(arg) , get<2>(arg) ); } }; template<> //_________________________________ struct Apply<4> ///< Apply function with 4 Arguments { template static RET invoke (FUN& f, TUP & arg) { return f ( get<0>(arg) , get<1>(arg) , get<2>(arg) , get<3>(arg) ); } template static RET bind (FUN& f, TUP & arg) { return std::bind (f, get<0>(arg) , get<1>(arg) , get<2>(arg) , get<3>(arg) ); } }; template<> //_________________________________ struct Apply<5> ///< Apply function with 5 Arguments { template static RET invoke (FUN& f, TUP & arg) { return f ( get<0>(arg) , get<1>(arg) , get<2>(arg) , get<3>(arg) , get<4>(arg) ); } template static RET bind (FUN& f, TUP & arg) { return std::bind (f, get<0>(arg) , get<1>(arg) , get<2>(arg) , get<3>(arg) , get<4>(arg) ); } }; template<> //_________________________________ struct Apply<6> ///< Apply function with 6 Arguments { template static RET invoke (FUN& f, TUP & arg) { return f ( get<0>(arg) , get<1>(arg) , get<2>(arg) , get<3>(arg) , get<4>(arg) , get<5>(arg) ); } template static RET bind (FUN& f, TUP & arg) { return std::bind (f, get<0>(arg) , get<1>(arg) , get<2>(arg) , get<3>(arg) , get<4>(arg) , get<5>(arg) ); } }; template<> //_________________________________ struct Apply<7> ///< Apply function with 7 Arguments { template static RET invoke (FUN& f, TUP & arg) { return f ( get<0>(arg) , get<1>(arg) , get<2>(arg) , get<3>(arg) , get<4>(arg) , get<5>(arg) , get<6>(arg) ); } template static RET bind (FUN& f, TUP & arg) { return std::bind (f, get<0>(arg) , get<1>(arg) , get<2>(arg) , get<3>(arg) , get<4>(arg) , get<5>(arg) , get<6>(arg) ); } }; template<> //_________________________________ struct Apply<8> ///< Apply function with 8 Arguments { template static RET invoke (FUN& f, TUP & arg) { return f ( get<0>(arg) , get<1>(arg) , get<2>(arg) , get<3>(arg) , get<4>(arg) , get<5>(arg) , get<6>(arg) , get<7>(arg) ); } template static RET bind (FUN& f, TUP & arg) { return std::bind (f, get<0>(arg) , get<1>(arg) , get<2>(arg) , get<3>(arg) , get<4>(arg) , get<5>(arg) , get<6>(arg) , get<7>(arg) ); } }; template<> //_________________________________ struct Apply<9> ///< Apply function with 9 Arguments { template static RET invoke (FUN& f, TUP & arg) { return f ( get<0>(arg) , get<1>(arg) , get<2>(arg) , get<3>(arg) , get<4>(arg) , get<5>(arg) , get<6>(arg) , get<7>(arg) , get<8>(arg) ); } template static RET bind (FUN& f, TUP & arg) { return std::bind (f, get<0>(arg) , get<1>(arg) , get<2>(arg) , get<3>(arg) , get<4>(arg) , get<5>(arg) , get<6>(arg) , get<7>(arg) , get<8>(arg) ); } }; /* ===== Helpers for partial function application ===== */ /** @note relying on the implementation type * since we need to _build_ placeholders */ using std::_Placeholder; /** * Build a list of standard function argument placeholder types. * For each of the elements of the provided reference list, * a Placeholder is added, numbers counting up starting with 1 (!) */ template struct PlaceholderTuple : PlaceholderTuple { }; template struct PlaceholderTuple, i> { using TailPlaceholders = typename PlaceholderTuple::List; using List = Node<_Placeholder, TailPlaceholders>; }; template struct PlaceholderTuple { using List = Nil; }; using std::tuple_element; using std::tuple_size; using std::get; /** * 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 * (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 * then be stored into the desired positions. Later on, when actually invoking * such a partially closed function, only the arguments marked with placeholders * need to be supplied, while the other arguments will use the values hereby * "baked" into the closure. * @tparam TAR full target tuple type. Some oft the elements within this tuple will * be default constructed, some will be initialised from the SRC tuple * @tparam SRC argument tuple type, for the values _actually to be initialised_ here. * @tparam start position within \a SRC, at which the sequence of init-arguments starts; * all other positions will just be default initialised * @see lib::meta::TupleConstructor */ template struct PartiallyInitTuple { template using DestType = typename std::tuple_element::type; /** * define those index positions in the target tuple, * where init arguments shall be used on construction. * All other arguments will just be default initialised. */ static constexpr bool useArg (size_t idx) { return (start <= idx) and (idx < start + std::tuple_size()); } template struct IndexMapper { SRC const& initArgs; operator DestType() { return std::get (initArgs); } }; template struct IndexMapper { SRC const& initArgs; operator DestType() { return DestType(); } }; }; } // (END) impl-namespace /* ======= core operations: closures and partial application ========= */ /** * Closure-creating template. * The instance is linked (reference) to a given concrete argument tuple. * A functor with a matching signature may then either be _closed_ over * this argument values, or even be invoked right away with theses arguments. * @warning we take functor objects _and parameters_ by reference */ template class TupleApplicator { using Args = typename _Fun::Args; using Ret = typename _Fun::Ret; using BoundFunc = function; enum { ARG_CNT = count() }; /** storing a ref to the parameter tuple */ Tuple& params_; public: TupleApplicator (Tuple& args) : params_(args) { } BoundFunc bind (SIG& f) { return Apply::template bind (f, params_); } BoundFunc bind (function const& f) { return Apply::template bind (f, params_); } Ret operator() (SIG& f) { return Apply::template invoke (f, params_); } Ret operator() (function& f) { return Apply::template invoke (f, params_); } }; /** * Closing a function over its arguments. * This is a small usage example or spin-off, * having almost the same effect than invoking `std::bind()`. * The notable difference is that the function arguments for * creating the closure are passed in as one tuple compound. */ template class FunctionClosure { typedef typename _Fun::Args Args; typedef typename _Fun::Ret Ret; function closure_; public: FunctionClosure (SIG& f, Tuple& arg) : closure_(TupleApplicator(arg).bind(f)) { } FunctionClosure (function const& f, Tuple& arg) : closure_(TupleApplicator(arg).bind(f)) { } Ret operator() () { return closure_(); } typedef Ret result_type; ///< for STL use typedef void argument_type; }; /** * Partial function application * Takes a function and a value tuple, * using the latter to close function arguments * either from the front (left) or aligned to the end * of the function argument list. Result is a "reduced" function, * expecting only the remaining "un-closed" arguments at invocation. * @tparam SIG signature of the function to be closed, either as * function reference type or `std::function` object * @tparam VAL type sequence describing the tuple of values * used for closing arguments * @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. */ template class PApply { using Args = typename _Fun::Args; using Ret = typename _Fun::Ret; using ArgsList = typename Args::List; using ValList = typename VAL::List; using ValTypes = typename TySeq::Seq; // reconstruct a type-seq from a type-list enum { ARG_CNT = count() , VAL_CNT = count() , ROFFSET = (VAL_CNT < ARG_CNT)? ARG_CNT-VAL_CNT : 0 }; // create list of the *remaining* arguments, after applying the ValList using LeftReduced = typename Splice::Back; using RightReduced = typename Splice::Front; using ArgsL = typename TySeq::Seq; using ArgsR = typename TySeq::Seq; // build a list, where each of the *remaining* arguments is replaced by a placeholder marker 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 using LeftReplaced = typename Splice::List; using RightReplaced = typename Splice::List; using LeftReplacedTypes = typename TySeq::Seq; using RightReplacedTypes = typename TySeq::Seq; // create a "builder" helper, which accepts exactly the value tuple elements // and puts them at the right location, while default-constructing the remaining // (=placeholder)-arguments. Using this builder helper, we can finally set up // the argument tuples (Left/RightReplacedArgs) used for the std::bind call template using IdxSelectorL = typename PartiallyInitTuple::template IndexMapper; template using IdxSelectorR = typename PartiallyInitTuple::template IndexMapper; using BuildL = TupleConstructor; using BuildR = TupleConstructor; /** Tuple to hold all argument values, starting from left. * Any remaining positions behind the substitute values are occupied by binding placeholders */ using LeftReplacedArgs = Tuple; /** Tuple to hold all argument values, aligned to the end of the function argument list. * Any remaining positions before the substitute values are occupied by binding placeholders */ using RightReplacedArgs = Tuple; public: using LeftReducedFunc = function::Sig>; using RightReducedFunc = function::Sig>; /** do a partial function application, closing the first arguments
* `f(a,b,c)->res + (a,b)` yields `f(c)->res` * * @param f function, function pointer or functor * @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 arg) { LeftReplacedArgs params {BuildL(std::move(arg))}; return func::Apply::template bind (f, params); } /** do a partial function application, closing the last arguments
* `f(a,b,c)->res + (b,c)` yields `f(a)->res` * * @param f function, function pointer or functor * @param arg value tuple, used to close function arguments, aligned to the right end. * @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. */ static RightReducedFunc bindBack (SIG const& f, Tuple arg) { RightReplacedArgs params {BuildR(std::move(arg))}; return func::Apply::template bind (f, params); } }; /** * Bind a specific argument to an arbitrary value. * Especially, this "value" might be another binder. */ template class BindToArgument { using Args = typename _Fun::Args; using Ret = typename _Fun::Ret; using ArgsList = typename Args::List; using ValList = typename TySeq::List; enum { ARG_CNT = count() }; 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 TySeq::Seq; using RemainingArgs = typename TySeq::Seq; using ReducedSig = typename BuildFunType::Sig; template using IdxSelector = typename PartiallyInitTuple::template IndexMapper; using BuildPreparedArgs = TupleConstructor; public: using ReducedFunc = function; static ReducedFunc reduced (SIG& f, X val) { Tuple params {BuildPreparedArgs{std::make_tuple (val)}}; return func::Apply::template bind (f, params); } }; namespace { // ...helpers for specifying types in function declarations.... using std::get; using util::unConst; template struct _Sig { using Type = typename BuildFunType::Sig; using Applicator = TupleApplicator; }; template struct _Clo { using Ret = typename _Fun::Ret; using Signature = typename _Sig::Type; using Type = FunctionClosure; }; template struct _Chain { using Ret = typename _Fun::Ret; using Args = typename _Fun::Args; using FunType = typename BuildFunType::Fun; static auto adaptedFunType() { return FunType{}; } template static auto composedFunctions (F1&& f1, F2&& f2, _Fun) { tuple binding{forward (f1) ,forward (f2) }; return [binding = move(binding)] (ARGS ...args) -> RET { auto& functor1 = get<0>(binding); auto& functor2 = get<1>(binding); // return functor2 (functor1 (forward (args)...)); }; } }; 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 /* ========== function-style interface ============= */ /** build a TupleApplicator, which embodies the given * argument tuple and can be used to apply them * to various functions repeatedly. */ template inline typename _Sig>::Applicator tupleApplicator (std::tuple& args) { using Signature = typename _Sig>::Type; return TupleApplicator{args}; } /** apply the given function to the argument tuple * @deprecated 11/23 meanwhile provided by the standard lib! */ template inline typename _Fun::Ret apply (SIG& f, std::tuple& args) { using Ret = typename _Fun::Ret; // using Signature = typename _Sig>::Type; // Note: deliberately re-building the Signature Type return TupleApplicator{args} (f); // in order to get better error messages here } /** close the given function over all arguments, * using the values from the argument tuple. * @return a closure object, which can be * invoked later to yield the * function result. */ template inline typename _Clo>::Type closure (SIG& f, std::tuple& args) { using Closure = typename _Clo>::Type; return Closure (f,args); } /** close the given function over the first argument. * @warning never tie an ownership-managing object by-value! */ template inline auto applyFirst (FUN&& fun, ARG&& arg) { static_assert (_Fun(), "expect something function-like"); return _PapS::bindFrontArg (forward (fun) ,forward (arg) ,_PapS::adaptedFunType()); } /** close the given function over the last argument */ template inline auto applyLast (FUN&& fun, ARG&& 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)); } /** build a functor chaining the given functions: feed the result of f1 into f2. * @note the mathematical notation would be `chained ≔ f2 ∘f1` */ template inline auto chained (FUN1&& f1, FUN2&& f2) { static_assert (_Fun(), "expect something function-like for function-1"); static_assert (_Fun(), "expect something function-like for function-2"); using Chain = _Chain; return Chain::composedFunctions (forward (f1) ,forward (f2) ,Chain::adaptedFunType()); } }}} // namespace lib::meta::func #endif