Given that the function isn't known at compile time, you won't get any faster than a function pointer/reference.
The advantage of std::function
is that it would allow you to take, say, a functor, member function pointer or lambda expressions. But there is some overhead.
As one comment mentioned, I would replace the const double &
args with double
. Size is the same on most platforms these days and it removes a dereference.
Here is an example using std::function
:
#include <iostream>
#include <functional>
#include <math.h>
double multiply(double x, double y) { return x * y; }
double add(double x, double y) { return x + y; }
class equation
{
public:
using ComputeFunction_t = std::function<double(double, double)>;
template <typename FunctionPtr>
equation(FunctionPtr pfn)
: computeFunction_m(pfn)
{ }
void compute(double d1, double d2)
{
printf("(%f, %f) => %f\n", d1, d2, computeFunction_m(d1, d2));
}
protected:
ComputeFunction_t computeFunction_m;
};
int main() {
equation prod(multiply);
prod.compute(10, 20); // print 200
equation sum(add);
sum.compute(10, 20); // print 30
equation hypotenuse([](double x, double y){ return sqrt(x*x + y*y); });
hypotenuse.compute(3, 4); // print 5
struct FooFunctor
{
FooFunctor(double d = 1.0) : scale_m(d) {}
double operator()(double x, double y) { return scale_m * (x + y); }
private:
double scale_m;
};
equation fooadder(FooFunctor{});
fooadder.compute(10, 20); // print 30
equation fooadder10(FooFunctor{10.0});
fooadder10.compute(10, 20);
struct BarFunctor
{
BarFunctor(double d = 1.0) : scale_m(d) {}
double scaledAdd(double x, double y) { return scale_m * (x + y); }
private:
double scale_m;
};
BarFunctor bar(100.0);
std::function<double(double,double)> barf = std::bind(&BarFunctor::scaledAdd, &bar, std::placeholders::_1, std::placeholders::_2);
equation barfadder(barf);
barfadder.compute(10, 20); // print 3000
return 0;
}
But, again, this gain in flexibility does have a small runtime cost. Whether its worth the cost depends on the application. I'd probably lean toward generality and a flexible interface first and then profile later to see if it is a real issue for the sorts of functions that will be used in practice.
If you can make your solver into a header-only library, then when the user provides inline-able functions in his code, you may be able to get better performance. For instance:
template <typename ComputeFunction>
class Equation
{
public:
Equation(ComputeFunction fn)
: computeFunction_m(fn)
{ }
void compute(double d1, double d2)
{
printf("(%f, %f) => %f\n", d1, d2, computeFunction_m(d1, d2));
}
protected:
ComputeFunction computeFunction_m;
};
template <typename ComputeFunction>
auto make_equation(ComputeFunction &&fn)
{
return Equation<ComputeFunction>(fn);
}
Your instantiation of the Equation
class now can completely inline the execution of the function. Calling is very similar, given the make_equation
function (the above implementation assumes C++14, but the C++11 version isn't much different):
auto fooadder2 = make_equation(FooFunctor{});
fooadder2.compute(10, 20);
auto hypot2 = make_equation([](double x, double y){ return sqrt(x*x + y*y); });
hypot2.compute(3, 4);
With full optimization you'll likely only find the call to printf
with the results of the calculation in the compiled code.