Problem
I have a function double subs(std::function<double(double)> func)
, and I want to wrap it in a new function that looks like this
template<typename... Args> double subsWrap(std::function<double(Args... args)> func)
that applies subs
to some function that takes more inputs as
subs( subs( subs( ... func(...) ) ) )
with each subs
applied to only one of the arguments of func
at a time.
Minimal example
Let's say that we have a function
auto subs = [] (std::function<double(double)> func){return func(2) func(5.3);};
and we want to apply it to
auto f2=[](double x, double y){return std::sin(x*std::exp(x/y)); };
as
subs( [&f2](double y){ return subs( [&y,&f2](double x){ return f2(x,y); } ); } )
For f2
, this is easy, so there is no need for a wrapper. However, if we want to do the same thing for a function of a greater number of arguments (e.g. double(double,double,double,double,double)
) things start to become complicated.
There has to be a way to do this automatically, but I am not even sure how to start.
CodePudding user response:
What about using variadic lambdas together with std::is_invocable
type trait to terminate recursion?
template<class Fn>
double subs_wrap(Fn func) {
if constexpr (std::is_invocable_v<Fn, double>)
return subs(func);
else
return subs([=](double x) {
return subs_wrap(
[=](auto... xs) -> decltype(func(x, xs...))
{ return func(x, xs...); }
);
});
}
Here an explicit return type specification for a lambda is needed to propagate "invocability" property. [=](auto... xs) { return func(x, xs...); }
is formally invocable with any number of arguments, no matter whether func(x, xs...)
is invocable or not. When the return type is specified explicitly with decltype
, SFINAE jumps in.
With this implementation, both expressions
subs([=](double y) {
return subs([=](double x) {
return f2(x, y);
});
});
and
subs_wrap(f2);
will produce the same result.
It's interesting to note that with -O3
optimization both GCC and Clang can optimize all this code away and replace subs_wrap(f2)
with a compile-time constant. With similar code written using std::function
arguments they don't do it.
How do we do the unpacking if we want to pass arguments to subs (different for each recursion)
Here is a way to achieve this with a slight modification of code:
template<class Fn, class P, class... Ps>
double subs_wrap(Fn func, P p, Ps... ps) {
if constexpr (std::is_invocable_v<Fn, double>) {
static_assert(sizeof...(Ps) == 0);
return subs(func, p);
}
else {
static_assert(sizeof...(Ps) > 0);
return subs([=](double x) {
return subs_wrap(
[=](auto... xs) -> decltype(func(x, xs...))
{ return func(x, xs...); },
ps...);
}, p);
}
}
subs_wrap(f2, p1, p2);