LUMIERA.clone/src/lib/meta/function-closure.hpp
Ichthyostega 20f3252892 Upgrade: down with typename!!
Yet another chainsaw massacre.

One of the most obnoxious annoyances with C++ metaprogramming
is the need to insert `typename` and `template` qualifiers into
most definitions, to help the compiler to cope with the syntax,
which is not context-free.

The recent standards adds several clarifications, so that most
of these qualifiers are redundant now, at least at places where
it is unambiguously clear that only a type can be given.

GCC already supports most of these relaxing rules
(Clang unfortunately lags way behind with support of newer language features...)
2025-07-06 01:19:08 +02:00

543 lines
22 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
FUNCTION-CLOSURE.hpp - metaprogramming tools for closing a function over given arguments
Copyright (C)
2009, Hermann Vosseler <Ichthyostega@web.de>
  **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 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).
** Further improvement of the implementation is thus mandated... ////////////////////////////////////TICKET #1394
**
** @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 <functional>
#include <utility>
#include <tuple>
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
/** @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<typename TYPES, size_t i=1>
struct PlaceholderTuple
: PlaceholderTuple<typename TYPES::List>
{ };
template<typename X, typename TAIL, size_t i>
struct PlaceholderTuple<Node<X,TAIL>, i>
{
using TailPlaceholders = PlaceholderTuple<TAIL,i+1>::List;
using List = Node<_Placeholder<i>, TailPlaceholders>;
};
template<size_t i>
struct PlaceholderTuple<Nil, i>
{
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
* 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<i>`.
* 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<typename SRC, typename TAR, size_t start>
struct PartiallyInitTuple
{
template<size_t i>
using DestType = std::tuple_element_t<i, TAR>;
/**
* 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<SRC>());
}
template<size_t idx, bool doPick = PartiallyInitTuple::useArg(idx)>
struct IndexMapper
{
SRC&& initArgs;
operator DestType<idx>()
{ // std::get passes-through reference kind
return std::get<idx-start> (forward<SRC>(initArgs));
}
};
template<size_t idx>
struct IndexMapper<idx, false>
{
SRC&& initArgs;
operator DestType<idx>()
{
return DestType<idx>();
}
};
};
/**
* Helper to package a given invokable,
* so that it has a well defined function signature.
* @warning defined argument types must be suitable.
* @remark need to define the wrapper functor class explicitly,
* to be able to work around any const / non-const cases;
* we can not overload `operator()` because then `decltype(operator())`
* would be ambiguous, breaking the detection of the function signature.
*/
template<typename...ARGS>
struct AdaptInvokable
{
template<class FUN>
static auto
buildWrapper (FUN&& fun)
{
struct Wrap
{
FUN fun_;
auto
operator() (ARGS... args)
{
return fun_(forward<ARGS>(args)...);
}
};
return Wrap{forward<FUN>(fun)};
}
};
} // (END) impl-namespace
/* ======= core operations: closures and partial application ========= */
/**
* Base technique: build a binder with arguments from a tuple.
* @remark based on the apply-λ-trick by David Vandervoorde
* to unpack the tuple elements as argument pack
* @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 »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
* argument positions marked with `std::_Placeholder<N>` instances
* will remain _open_ to accept arguments on the resulting function.
*/
template<class FUN, class TUP> requires(tuple_like<remove_reference_t<TUP>>)
auto
bindArgTuple (FUN&& fun, TUP&& tuple)
{
return lib::meta::apply ([functor = forward<FUN>(fun)]
(auto&&... args)
{
return std::bind (move(functor)
,forward<decltype(args)> (args) ...);
}
,std::forward<TUP> (tuple));
}
/**
* Workaround to yield std::bind functors
* with a clearly defined function signature.
* @tparam TYPES type-sequence or type-list of the function arguments to take
* @remark the result of `std::bind` exposes several overloaded `operator()`,
* which unfortunately defeats detection of the resulting function signature
* by downstream code, which needs this information to guide further processing.
* @see TupleClosureBuilder::wrapBuilder(closureFun)
*/
template<class TYPES, class FUN>
auto
buildInvokableWrapper (FUN&& fun)
{
static_assert (is_Typelist<TYPES>::value);
using ArgTypes = TYPES::Seq;
using Builder = lib::meta::RebindVariadic<AdaptInvokable, ArgTypes>::Type;
return Builder::buildWrapper (forward<FUN> (fun));
}
/**
* 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 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<typename SIG, typename VAL>
class PApply
{
using Args = _Fun<SIG>::Args;
using Ret = _Fun<SIG>::Ret;
using ArgsList = Args::List;
using ValList = VAL::List;
using ValTypes = Types<ValList>::Seq; // reconstruct a type-seq from a type-list
enum { ARG_CNT = count<ArgsList>()
, VAL_CNT = count<ValList>()
, ROFFSET = (VAL_CNT < ARG_CNT)? ARG_CNT-VAL_CNT : 0
};
// create list of the *remaining* arguments, after applying the ValList
using LeftReduced = Splice<ArgsList, ValList>::Back;
using RightReduced = Splice<ArgsList, ValList, ROFFSET>::Front;
using ArgsL = Types<LeftReduced>::Seq;
using ArgsR = Types<RightReduced>::Seq;
// build a list, where each of the *remaining* arguments is replaced by a placeholder marker
using TrailingPlaceholders = func::PlaceholderTuple<LeftReduced>::List;
using LeadingPlaceholders = func::PlaceholderTuple<RightReduced>::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 = Splice<ArgsList, TrailingPlaceholders, VAL_CNT>::List;
using RightReplaced = Splice<ArgsList, LeadingPlaceholders, 0 >::List;
using LeftReplacedTypes = Types<LeftReplaced>::Seq;
using RightReplacedTypes = Types<RightReplaced>::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<class SRC, class TAR, size_t i>
using IdxSelectorL = PartiallyInitTuple<SRC, TAR, 0>::template IndexMapper<i>;
template<class SRC, class TAR, size_t i>
using IdxSelectorR = PartiallyInitTuple<SRC, TAR, ROFFSET>::template IndexMapper<i>;
using BuildL = TupleConstructor<LeftReplacedTypes, IdxSelectorL>;
using BuildR = TupleConstructor<RightReplacedTypes, IdxSelectorR>;
/** Tuple to hold all argument values, starting from left.
* Any remaining positions behind the substitute values are occupied by binding placeholders */
using LeftReplacedArgs = Tuple<LeftReplacedTypes>;
/** 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<RightReplacedTypes>;
public:
using LeftReducedFunc = function<typename BuildFunType<Ret,ArgsL>::Sig>;
using RightReducedFunc = function<typename BuildFunType<Ret,ArgsR>::Sig>;
/** do a partial function application, closing the first arguments<br/>
* `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, and consequently std::bind _must take the 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.
* @todo 5/2025 seems indeed we could perfect-forward everything into the binder object.
*/
static LeftReducedFunc
bindFront (SIG const& f, Tuple<ValTypes> arg)
{
LeftReplacedArgs params {BuildL(std::move(arg))};
return bindArgTuple (f, params);
}
/** do a partial function application, closing the last arguments<br/>
* `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<ValTypes> arg)
{
RightReplacedArgs params {BuildR(std::move(arg))};
return bindArgTuple (f, params);
}
};
/**
* 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<typename SIG, typename X, uint pos>
class BindToArgument
{
using Args = _Fun<SIG>::Args;
using Ret = _Fun<SIG>::Ret;
using ArgsList = Args::List;
using ValList = Types<X>::List;
enum { ARG_CNT = count<ArgsList>() };
using RemainingFront = Splice<ArgsList, ValList, pos>::Front;
using RemainingBack = Splice<ArgsList, ValList, pos>::Back;
using PlaceholdersBefore = func::PlaceholderTuple<RemainingFront>::List;
using PlaceholdersBehind = func::PlaceholderTuple<RemainingBack,pos+1>::List;
using PreparedArgsRaw = typename Append<typename Append<PlaceholdersBefore // arguments before the splice: passed-through
,ValList >::List // splice in the value tuple
,PlaceholdersBehind // arguments behind the splice: passed-through
>::List;
using PreparedArgs = Prefix<PreparedArgsRaw, ARG_CNT>;
using ReducedArgs = Append<RemainingFront, RemainingBack>::List;
using PreparedArgTypes = Types<PreparedArgs>::Seq;
using RemainingArgs = Types<ReducedArgs>::Seq;
template<class SRC, class TAR, size_t i>
using IdxSelector = PartiallyInitTuple<SRC, TAR, pos>::template IndexMapper<i>;
using BuildPreparedArgs = TupleConstructor<PreparedArgTypes, IdxSelector>;
public:
template<class FUN, class VAL>
static auto
reduced (FUN&& f, VAL&& val)
{
Tuple<PreparedArgTypes> bindingTuple {
BuildPreparedArgs{
std::forward_as_tuple (
forward<VAL>(val))}};
return buildInvokableWrapper<RemainingArgs>(
bindArgTuple (forward<FUN>(f)
, move(bindingTuple)));
}
};
namespace { // ...helpers for specifying types in function declarations....
using std::get;
using util::unConst;
template<typename FUN1, typename FUN2>
struct _Chain
{
using Ret = _Fun<FUN2>::Ret;
using Args = _Fun<FUN1>::Args;
using FunType = BuildFunType<Ret,Args>::Fun;
static auto adaptedFunType() { return FunType{}; }
template<typename F1, typename F2
,typename RET, typename... ARGS>
static auto
composedFunctions (F1&& f1, F2&& f2, _Fun<RET(ARGS...)>)
{
tuple<F1,F2> binding{forward<F1> (f1)
,forward<F2> (f2)
};
return [binding = move(binding)]
(ARGS ...args) -> RET
{
auto& functor1 = get<0>(binding);
auto& functor2 = get<1>(binding);
//
return functor2 (functor1 (forward<ARGS> (args)...));
};
}
};
} // (End) argument type shortcuts
/* ========== function-style interface ============= */
/** 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<typename FUN, typename TERM>
inline auto
bindFirst (FUN&& f, TERM&& arg)
{
return BindToArgument<FUN,TERM, 0>::reduced (std::forward<FUN> (f)
,std::forward<TERM> (arg));
}
template<typename FUN, typename TERM>
inline auto
bindLast (FUN&& f, TERM&& arg)
{
enum { LAST_POS = -1 + count<typename _Fun<FUN>::Args>() };
return BindToArgument<FUN,TERM,LAST_POS>::reduced (std::forward<FUN> (f)
,std::forward<TERM> (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<typename FUN1, typename FUN2>
inline auto
chained (FUN1&& f1, FUN2&& f2)
{
static_assert (_Fun<FUN1>(), "expect something function-like for function-1");
static_assert (_Fun<FUN2>(), "expect something function-like for function-2");
using Chain = _Chain<FUN1,FUN2>;
return Chain::composedFunctions (forward<FUN1> (f1)
,forward<FUN2> (f2)
,Chain::adaptedFunType());
}
}}} // namespace lib::meta::func
#endif