Передача аргументов в указатель на функцию

Рейтинг: 2Ответов: 2Опубликовано: 15.06.2023

Есть следующий код, который позволяет решать уравнение методом касательных:

#include <iostream>

double q_func(double x, double k, double y)
{
    return x * pow(1 - (k-1)/(k+1) * x * x, 1/(k-1)) * pow((k+1)/2, 1/(k-1)) - y;
}

double solve(double (*f)(double, double, double), double k, double y, double x_0=2.3, int n=50)
{
    double x = x_0;
    double dx = 1e-5;
    double df = (f(x+dx, k, y)-f(x, k, y))/dx;

    for (int i=0; i<=n;i++){
        x_0 = x;
        x -= f(x, k, y) / df;
        if (abs(x - x_0) < 1e-6)
            return x;
    }
    return x;
}

int main() {
    double x = solve(q_func, 1.4, 0.25);
    std::cout << x << std::endl;
    return 0;
}

Функция q_func принимает 3 аргумента и сейчас, чтобы решить уравнение (найти x) я передаю в функцию solve дополнительно два аргумента k и y.

Получается, что при добавлении параметра в функцию q_func мне необходимо дополнительно переписывать и функцию solve. При этом уравнение с двумя параметрами уже решить нельзя будет, нужно будет сохранить старую сигнатуру, получается дублирование кода. Можно ли как-то этого избежать?

Первично в голову приходить передавать vector или map вторым аргументом, но может есть более С++-path решение?

Ответы

▲ 3Принят

f - это функция от x. k не её параметр, а деталь реализации. rhs тоже не аргумент, а значение y в уравнении y = f(x). Так что надо делать f функцией одного параметра, а всё остальное из сигнатуры исключить.

В современном C++ для передачи в функцию настроек лучше всего подходит лямбда. Функцию solve придётся сделать шаблонной. k передаётся в f через замыкание:

#include <cmath>
#include <iostream>

template <typename Func>
double solve(Func f, double y, double x_0 = 2.3, int n = 50) {
    double x = x_0;
    double dx = 1e-5;

    for (int i = 0; i < n; ++i) {
        x_0 = x;
        double df = (f(x + dx) - f(x)) / dx;
        x -= (f(x) - y) / df;
        if (x - x_0 < 1e-6) {
            return x;
        }
    }
    return x;
}

int main() {
    double k = 1.4;
    auto q_func = [k](double x) {
        return
            x *
            pow(1 - (k - 1) / (k + 1) * x * x, 1 / (k - 1)) *
            pow((k + 1) / 2, 1 / (k - 1))
        ;
    };
    std::cout << solve(q_func, 0.25) << std::endl;
}

Если вам не нравится лямбда и шаблонный код, есть более традиционный способ: завести интерфейс с double operator()(double x) и работать с его наследниками. Получится что-то такое:

#include <cmath>
#include <iostream>

class Func {
public:
    virtual ~Func() {}
    virtual double operator()(double x) const = 0;
};

double solve(const Func &f, double y, double x_0 = 2.3, int n = 50) {
    double x = x_0;
    double dx = 1e-5;

    for (int i = 0; i < n; ++i) {
        x_0 = x;
        double df = (f(x + dx) - f(x)) / dx;
        x -= (f(x) - y) / df;
        if (x - x_0 < 1e-6) {
            return x;
        }
    }
    return x;
}

class QFunc : public Func {
public:
    QFunc(double k) : k(k) {}
    virtual double operator()(double x) const override {
        return
            x *
            pow(1 - (k - 1) / (k + 1) * x * x, 1 / (k - 1)) *
            pow((k + 1) / 2, 1 / (k - 1))
        ;
    }
private:
    double k;
};

int main() {
    QFunc q_func(1.4);
    std::cout << solve(q_func, 0.25) << std::endl;
}

P.S. Что касается математики, df лучше пересчитывать внутри цикла. Иначе не работают признаки гарантирующие сходимость итераций к корню и скорость этой сходимости.

▲ 1

Есть несколько подходов как решить эту проблему. Можно передать в функцию solve всегда только функцию принимающую только один параметр x. А если надо передать функцию с большим количеством параметров, то просто создать функцию обёртку с 1 параметром x.

#include <math.h>

#include <iostream>

double q_func(double x, double k, double rhs) {
  return x * pow(1 - (k - 1) / (k + 1) * x * x, 1 / (k - 1)) *
             pow((k + 1) / 2, 1 / (k - 1)) -
         rhs;
}

double solve(double (*f)(double), double x_0 = 2.3, int n = 50) {
  double x = x_0;
  double dx = 1e-5;
  double df = (f(x + dx) - f(x)) / dx;

  for (int i = 0; i <= n; i++) {
    x_0 = x;
    x = x - f(x) / df;
    if (x - x_0 < 1e-6) {
      return x;
    }
  }
  return x;
}

int main() {
  auto wrapper = [](double x) { return q_func(x, 1.4, 0.25); };
  double x = solve(wrapper);
  std::cout << x << std::endl;

  return 0;
}

Можно сделать так чтобы solve принимала любые 

Это сработает если k, rhs и остальные параметры известны на этапе компиляции. Если нет то не получится сконвертировать лямбду в указатель на функцию. Можно добавить немного концептов чтобы принимать что угодно вызываемое. Это позволяет передавать лямбды с неизвестными на этапе компиляции параметрами.

#include <math.h>

#include <concepts>
#include <iostream>

double q_func(double x, double k, double rhs) {
  return x * pow(1 - (k - 1) / (k + 1) * x * x, 1 / (k - 1)) *
             pow((k + 1) / 2, 1 / (k - 1)) -
         rhs;
}

template <typename Func>
  requires std::invocable<Func, double> &&
           std::same_as<double, std::invoke_result_t<Func, double>>
double solve(Func f, double x_0 = 2.3, int n = 50) {
  double x = x_0;
  double dx = 1e-5;
  double df = (f(x + dx) - f(x)) / dx;

  for (int i = 0; i <= n; i++) {
    x_0 = x;
    x = x - f(x) / df;
    if (x - x_0 < 1e-6) {
      return x;
    }
  }
  return x;
}

int main() {
  double k = 1.4;
  double rhs = 0.25;

  auto wrapper = [=](double x) { return q_func(x, k, rhs); };

  double x = solve(wrapper);
  std::cout << x << std::endl;

  auto wrapper2 =
      [](double first, double second) {
        return [=](double x) { return q_func(x, first, second); };
      };

  double x2 = solve(wrapper2(k, rhs));
  std::cout << x2 << std::endl;

  return 0;
}

Также можно использовать темплейты чтобы передавать любое число параметров в solve и из solve в переданную функцию. Если использовать этот подход параметры для функции должны идти в самом конце что не позволит n и x_0 быть параметрами по умолчанию.

#include <math.h>

#include <iostream>

double q_func(double x, double k, double rhs) {
  return x * pow(1 - (k - 1) / (k + 1) * x * x, 1 / (k - 1)) *
             pow((k + 1) / 2, 1 / (k - 1)) -
         rhs;
}

template <typename... Args>
double solve(double x_0, int n, double (*f)(double, Args...),
              Args... args) {
  double x = x_0;
  double dx = 1e-5;
  double df = (f(x + dx, args...) - f(x, args...)) / dx;
  for (int i = 0; i <= n; i++) {
    x_0 = x;
    x = x - f(x, args...) / df;
    if (x - x_0 < 1e-6) {
      return x;
    }
  }
  return x;
}

int main() {
  double k = 1.4;
  double rhs = 0.25;

  double x = solve(2.3, 50, q_func, k, rhs);
  std:: cout << x << std::endl;
  return 0;
}