包装一个接受 std::function<double(doule)> 的函数,以便传递接受更多参数的函数

Wrap a function that takes std::function<double(doule)> in order to pass functions that take more arguments

问题

我有一个函数 double subs(std::function<double(double)> func),我想将它包装在一个新函数中,如下所示

template<typename... Args> double subsWrap(std::function<double(Args... args)> func)

subs应用于需要更多输入的函数,如

subs( subs( subs( ... func(...) ) ) )

每个 subs 一次仅应用于 func 的参数之一。

最小示例

假设我们有一个函数

auto subs = [] (std::function<double(double)> func){return func(2) + func(5.3);};

我们想将它应用到

auto f2=[](double x, double y){return std::sin(x*std::exp(x/y)); };

作为

subs( [&f2](double y){ return  subs( [&y,&f2](double x){ return f2(x,y); } ); } ) 

对于f2,这很容易,所以不需要包装器。然而,如果我们想对一个有更多参数的函数做同样的事情(例如 double(double,double,double,double,double)),事情就会变得复杂起来。

必须有一种方法可以自动执行此操作,但我什至不确定如何开始。

如何使用可变参数 lambda 和 std::is_invocable 类型特征来终止递归?

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...); }
            );
        });
}

这里需要明确的 return lambda 类型规范来传播“可调用性”属性。 [=](auto... xs) { return func(x, xs...); } 可以使用任意数量的参数正式调用,无论 func(x, xs...) 是否可调用。当使用 decltype 明确指定 return 类型时,SFINAE 会跳入。

通过此实现,两个表达式

subs([=](double y) { 
    return subs([=](double x) {
        return f2(x, y);
    });
});

subs_wrap(f2);

将产生相同的结果。

值得注意的是,通过 -O3 优化,GCC 和 Clang 都可以 optimize all this code away 并将 subs_wrap(f2) 替换为 compile-time 常量。对于使用 std::function 个参数编写的类似代码,他们不会这样做。


How do we do the unpacking if we want to pass arguments to subs (different for each recursion)

这是一种通过稍微修改代码来实现此目的的方法:

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