1

I am trying to write a general integral function and I would like to implement it in such a way so that it can accept any mathematical function. That is, I would like to pass the math function as an input parameter. In pseudo-code: simpson_int(x*x). I've heard of the function template in <functional> but I don't really have experience with templates in C++.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
Ptheguy
  • 503
  • 4
  • 11
  • This is in C, but I guess it applies too (in C++ you have more possibilities): https://stackoverflow.com/questions/9410/how-do-you-pass-a-function-as-a-parameter-in-c – Rakete1111 Sep 09 '17 at 18:26
  • 3
    Begin by writing it with a single, hard-coded function. Once you've got that working, think about how to make it accept a function as an argument. – Pete Becker Sep 09 '17 at 18:27
  • I'm pretty sure you could do `simpson_int([](double x) { return x * x; })` and it would work like a function pointer. – melpomene Sep 09 '17 at 18:28
  • @PeteBecker: could I do something like `simpson_int( double (*f) (double,double)` then say `simpson_int(powx,2)` – Ptheguy Sep 09 '17 at 18:33

2 Answers2

3

There are some solutions that comes in my mind (and this is my approach at the problem, for sure there are more solution, and maybe what I'm pointing out is not the best), that consider the fact that you need to call the argument function more than once in the Simpson implementation (thus you need a "callable" argument):

Function pointer

Function pointers (more C than C++), where you declare with two arguments: the first one will be the pointer to a function with the specified types, while the second is the argument for your function. Lets make an example:

#include <iostream>

double power2(double x) {
  return x * x;
}

double simspon(double (*f)(double), double x) {
  return f(x);
}

int main() {
  std::cout << simspon(power2, 2);
  return 0;
}

In this case I have used no templates for reaching the result. But this will not take any function as first argument, but only a function that has as argument a double and returns a double.

I think that most of c++ programmer will suggest you to avoid this method.

Function pointer and templates

So you maybe want to expand the previous example using templates and making it more general. It is quite simple to redefine the function to accept a template (an abstract type) that you actually specify only when you use it in your code:

#include <iostream>

double power2(double x) {
  return x * x;
}

int power2int(int x) {
  return x * x;
}

template <class T, class P>
P simspon(T (*f)(P), P x) {
  return f(x);
}

int main() {
  std::cout << simspon<double, double>(power2, 2.0);
  std::cout << simspon<int, int>(power2int, 2);
  return 0;
}

T and P are two templates: the first one is used for describing the returned value of the function pointer, while the second specify the argument of the function pointer, and the returned value of simpson.So when you are writing template <class T, classP> you are actually informing the compiler that that you are using T and P as placeholder for different type. You will actually declare the type that you want later on, when you will call the function in the main. This is not good code but I'm building the path to understand templates. Also, you specify the type of your argument function when you actually call simpson, with the < >.

(Disclaimer: you should consider to use template <typename T ...> instead of class. But I'm used with the old class and there are situation in which typename cannot be used, there are a lot of questions on SO that dive into this.)

Using std::function

Instead of using a function pointer as argument you may want to create a variable that stores your function to be passed as argument of simpson. This bring several advantages, because they are actually an object inside your code that have some predictable behavior in some unwanted circumstances (for example, in case of a null function pointer you have to check the pointer itself and handle it, in case of std::function if there is no callable pointer it throws std::bad_function_call error)

Here an example, and it uses again templates, as before:

#include <iostream>
#include <functional>

double power2(double x) {
  return x * x;
}

int power2int(int x) {
  return x * x;
}

template <class T, class P>
P simspon(std::function<T(P)> f, P x) {
  return f(x);
}

int main() {
  std::function<double(double)> p_power2 = power2; 
  std::cout << simspon<double, double>(p_power2, 2.0);

  std::function<double(double)> p_power2int = power2int; 
  std::cout << simspon<int, int>(power2int, 2);
  return 0;
}

Using lambdas

lambdas are closure and in your case (if you can use the standard C++14) can be used alongside the auto keyword to achieve quite a general behavior, without the explicit use of templates. The closure are also able to capture part/the whole context, check the reference for this.

Let's see an example, in which I create two lambdas that receive different arguments and a simpson function that is quite general (actually it is not, is the compiler that defines different functions with respect to the call that you do).

#include <iostream>

auto lambda = [](auto x) { return x * x ; };
auto lambda_2 = [] (int x) { return x + 10; };

auto simpson(auto f, auto x) {
  return f(x);
}

int main() {
  std::cout << simpson(lambda, 2.0);
  std::cout << simpson(lambda_2, 1);
  return 0;
}

You need to compile it with the -std=c++14 flag. There are tons of advise that comes in my mind to suggest you to avoid to implement your code in this way, remember that it has only some illustrative purposes (I've more than exaggerated with the auto keyword).

Function objects (the Problem class)

Maybe an improvement for your case is to write a general class for the mathematical functions to integrate and pass the object to your function. This bring several advantages: you may want to save some of the integrative result inside your function or even write the stream operator to pretty print your problem. This is the solution employed typically by mathematical libraries.

In this extremely simple case, we have a class that is a problem. When you create a new instance for this class, a std::function is passed to the constructor and stored inside the class. The instance of the class is the argument for your simpson:

#include <iostream>
#include <functional>

template <class T, class P>
class Problem {
public:
  // Attributes
  std::function<T(P)> _f;

  // Constructor
  Problem(std::function<T(P)> f) : _f(f) {};

  // Making the object callable
  P operator()(P x) { return _f(x); }
};

template <class T, class P>
P simspon(Problem<T, P> p, P x) {
  return p(x);
}

int main() {
  Problem<double, double> prb([](double x) { return x * x; });
  std::cout << simspon<double, double>(prb, 2);
  return 0;
}
Matteo Ragni
  • 2,837
  • 1
  • 20
  • 34
0

Use std::function, like this for example:

#include <iostream>     // std::cout
#include <functional>   // std::function

int main()
{
    std::function<double(double)> simpson_int =([](double x) { return x * x; };
    std::cout << "simpson_int(4): " << simpson_int(4) << '\n';
    return 0;
}

which outputs:

simpson_int(4): 16

gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • Sorry, but I think that OP wants to build a Simpson integration scheme, thus he wants that `simpson_int` accepts as argument a closure, not "being a closure". – Matteo Ragni Sep 09 '17 at 19:30
  • Hmm @MatteoRagni I am not sure I understand, can you help improve the answer? – gsamaras Sep 09 '17 at 19:32