为带有移位参数的不同函数编写模板

Writing a template for varying functions with shifted arguments

我希望这个问题(及其可能的答案)在范围上足够普遍,以便对其他人也有用。

我正在尝试求解涉及 double 个函数 的乘积的数值问题,形式为

其中 是预定义函数,我将 传递给集成商。

复杂的是我的函数 不是静态的;积分是重复进行的,每次积分时 都有不同的形式,例如,在第一个循环中它可能是

秒变成

等等,其中 表示 函数向量的成员。在积分的每次迭代中,我先验地不知道 的形式是什么,只知道它将是 s 与常数系数的线性组合。

由于我传递给集成器的 涉及带有 "shifted" 参数的 ,因此在每次迭代时我都需要访问每个 ,因为我需要调用形式

double Y (double x, double y, double z){

  return (f(x+y) - f(x)) * (f(y+z) - f(y)) * A(z+y);

}

对于集成商(或者可能不是?)。

我的问题是,代表我的 的最佳方式是什么?我一直在阅读明显的候选者(模板,std::function),但我不确定如何 "impose" 将转移的参数转移到函数上。

我敢肯定,在另一个化身中,这是一个相当典型的编程设计问题。谁能给我一些关于如何处理它的建议?

编辑:感谢 Rostislav,尤其是 Gombat,他提出了最初的解决方案(尽管 Rostislav 的非常优雅)。在结束这个问题之前,也许我会在这里追加一个进一步的问题,即考虑(更一般的)情况,其中没有关于输出 f(x) 的详细信息(它是 f_i(x) 具有常数系数)在每次迭代中,而我们只知道 f(x) = F(x),其中 F(x) 是一个 double 函数,它再次由 f_i(x) 组成,但是现在以任意方式。所有其他条件都相同(从已知 f_0(x) 开始),在这种情况下我应该如何表示我的 F(x)

我在想,既然我们知道解的函数依赖性F(x)(它只依赖于一个参数x,是一个double,并且由已知的f_i(x)),也许可以写一个 "template"(对滥用命名法表示歉意,它 很有启发性)函数 F(x) 我可以传递给集成器,然后集成器 "unwraps" 要集成的函数的实际形式,尽管这可能很复杂。

根据评论,希望以下内容对您有所帮助:

class MyF
{
public:
  // typedef as abbreviation
  typedef std::function<double(const double)> func;

  MyF( const std::vector<func>& functions )
   : m_functions(functions)
#ifdef _DEBUG
   , m_lcUpdated(false)
#endif
  { }

  // f combines the inner functions depending on m_linearCombination
  double operator()(const double x)
  {
    assert( m_lcUpdated );
    assert( m_linearCombination.size() > 0 );
    assert( m_linearCombination.size() <= m_functions.size() );

    double ret(0.0);
    // if m_linearCombination has a value only for the first function
    // no need to run the loop for all functions, so we iterate only
    // over the linearCombination entries.
    for ( size_t i = 0; i < m_linearCombination.size(); i++ )
    {
      // if the factor is very small, no need for m_function call
      if ( std::abs(m_linearCombination) < DBL_EPSILON ) continue;

      ret += m_linearCombination[i] * m_functions[i](x);
    }

    m_lcUpdated = false;
    return ret;
  }

  void setLinearCombination(const std::vector<double>& lc)
  { 
      m_linearCombination = lc; 
#ifdef _DEBUG
      m_lcUpdated = true; 
#endif
  }


private:
  std::vector<double> m_linearCombination;
  std::vector<func> m_functions;
#ifdef _DEBUG
  bool m_lcUpdated;
#endif
};

MyF 的构造函数在第一次迭代中(或之前?您也可以在没有参数的情况下构造 MyF 之前获取函数 f_i(x) 并编写一个设置方法来设置 vectorstd::function 秒)。 私有成员 m_lcUpdated 检查在使用 operator().

调用函数之前是否更新了线性组合
std::vector<MyF::func> functions;

// ...fill the functions

// construct your function class
MyF F(functions);
// and the variable to fill in each iteration on how to linear combine the inner functions
std::vector<double> linearCombination;

// Provide the first linear combination (fill it before of course)
F.setLinearCombination(linearCombination);

// In each iteration, call the method combining inner functions
F(x); // it works like this, since it is the operator() which is overloaded for const double as input
// And provide the linear combination for the next iteration
F.setLinearCombination( linearCombination );

也可以将 linearCombinations 作为 std::map<size_t,double>,其中 first 是索引,这样您就不必遍历其值可能为零的元素,如果线性组合是可能的组合是例如f_0(x)+ f_2(x).

编辑:我实现了@Gombat 的解决方案,因为它更直观,但肯定还会看到下面 Rostislav 的回答。

鉴于你是在集成Y,在集成过程中很可能会调用很多次,所以最好避免使用std::function,因为编译器基本上无法内联对std::function 由于 std::function 内部使用的类型擦除。

相反,您可以直接使用 lambda。如果您可以控制集成过程的实现(或者它已经将被集成的函数类型作为模板参数),那么您可以避免所有 std::function 用法。否则,仅将 std::function 用于与集成过程的通信是有意义的,但应尽可能避免使用它们。下面是实现它的示例代码(实例here):

#include <iostream>
#include <string>

#include <tuple>
#include <functional>

template<typename... Fn>
class LinearCombination {
public:
    template<typename... Un>
    LinearCombination(Un&&... fs)
        : functions(std::forward<Un>(fs)...)
    {
        coefs.fill(0.0);
    }

    double operator()(double x) const
    {
        return evaluateImpl(x, std::integral_constant<size_t, sizeof...(Fn)-1>());
    }

    void setCoef(size_t i, double c)
    {
        coefs[i] = c;
    }

private:
    template<size_t I>
    double evaluateImpl(double x, std::integral_constant<size_t, I> index) const
    {
        return evaluateOne(x, index) + evaluateImpl<I - 1>(x, std::integral_constant<size_t, I - 1>());
    }

    template<size_t I>
    double evaluateImpl(double x, std::integral_constant<size_t, 0> index) const
    {
        return evaluateOne(x, index);
    }

    template<size_t I>
    double evaluateOne(double x, std::integral_constant<size_t, I>) const
    {
        auto coef = coefs[I];
        return coef == 0.0 ? 0.0 : coef * std::get<I>(functions)(x);
    }


    std::tuple<Fn...> functions;
    std::array<double, sizeof...(Fn)> coefs;
};

// This helper function is there just to avoid writing something like...
// LinearCombination<decltype(f1), decltype(f2)> when you use lambdas f1 and f2
template<typename... Fn>
auto make_linear_combination(Fn&&... fn)
{
    return LinearCombination<Fn...>{std::forward<Fn>(fn)...};
}

double A(double)
{
    return 1.0;
}

/// Integration 1.0
double integrate3D(double(*f)(double, double, double))
{
    return f(1, 2, 3);
}

struct YHelper {
    static double Y(double x, double y, double z)
    {
        return (f(x + y) - f(x)) * (f(y + z) - f(y)) * A(z + y);
    }

    static std::function<double(double)> f;
};

std::function<double(double)> YHelper::f;


/// Integration 2.0
template<typename Integrable>
double integrate3D_2(Integrable&& f)
{
    return f(1, 2, 3);
}

int main()
{
    auto f1 = [](double x) { return x; };
    auto f2 = [](double x) { return 2 * x; };

    auto lc = make_linear_combination(std::move(f1), std::move(f2));
    lc.setCoef(0, 1.0);
    lc.setCoef(1, -1.0);

    std::cout << lc(2.0) << "\n";

    YHelper::f = std::ref(lc);
    std::cout << integrate3D(&YHelper::Y) << "\n";

    auto Y = [&lc](double x, double y, double z) { return (lc(x + y) - lc(x)) * (lc(y + z) - lc(y)) * A(z + y); };
    std::cout << integrate3D_2(Y) << "\n";
}

请注意,在 Integration 1.0 下是您无法控制集成过程签名的情况下的示例实现。 Integration 2.0 下的代码不仅更简洁,而且性能也会更好。

PS:当然,在 evaluateOne 中比较 coef0.0 时要小心 - 假设只有当您将系数直接设置为文字 0.0 而不是任何计算的结果。否则,使用 abs(coef) < epsilon 和适合您的应用程序的 epsilon 值。