Home > Enterprise >  Wrap a function that takes std::function<double(doule)> in order to pass functions that take m
Wrap a function that takes std::function<double(doule)> in order to pass functions that take m

Time:04-18

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);
  • Related