Reputation: 17
In the code below, I cannot figure out a way of passing a member function to a generic root-finder.
#include <stdio.h>
double OneDimBisector(double (*fun)(float), float a, float b, float tol){
double val;
val = (*fun)(0.5*(b-a)); // actually: do proper bisection
return val;
}
class EOS {
public:
double S_array[10][10]; // actually: filled by constructor
double S(double T, double P);
double T_PS(double P, double S);
double functForT_PS(double T);
double (EOS::*pfunctForT_PS)(double);
double Sseek, Pseek;
};
double EOS::S(double T, double P){
double val = T+P; // actually: interpolate in S_array
return val;
}
double EOS::functForT_PS(double T){
return S(T,Pseek)-Sseek;
}
// Find T from P and S (T is invertible), assuming the intervals are ok
double EOS::T_PS(double P, double S0){
double Tmin = 2., Tmax = 7., T1, tol=1e-8;
pfunctForT_PS = &EOS::functForT_PS;
Sseek = S0;
Pseek = P;
printf("\n %f\n", (*this.*pfunctForT_PS)(4.)); // no problem
T1 = OneDimBisector(pfunctForT_PS, Tmin, Tmax, tol); // wrong type for pfunctForT_PS
return T1;
}
int main() {
double P=3., S=8;
EOS myEOS;
printf("\n %f %f %f\n",P,S,myEOS.T_PS(P,S));
}
I do not want to make the root-finder a member because it is not specific to this class, and the solution of making everything static
seems very inelegant. Would someone have an idea? This must be a common situation yet I did not find a relevant post that was also understandable to me.
Thanks!
Edit: Actually, I also meant to ask: Is there a proper, thread-safe way of setting the Pseek
variable other than what I did? Just to make it clear: I am doing one-dimensional root finding on a two-dimensional function but fixing one of the two arguments.
Upvotes: 0
Views: 1515
Reputation: 477580
One way would be to change the signature of the root finder (add #include <functional>
):
double OneDimBisector(std::function<double(float)> f, float a, float b, float tol);
Then invoke it with bind
:
T1 = OneDimBisector(std::bind(pfunctForT_PS, this, std::placeholders::_1),
Tmin, Tmax, tol);
This carries a certain overhead. If you don't mind having lots of duplicate code, you can make the function a template:
template <typename Func>
double OneDimBisector(Func f, float a, float b, float tol);
You invoke it the same way, but every time you have a new function type, a new instance of the template is created in your compilate.
The "traditional" solution would be to have a free (or static) function that accepts an additional instance argument.
Update: The "traditional solution":
double OneDimBisector(double(*f)(float, void *), void * data, ...);
double EOSBisect(float f, void * data)
{
EOS * e = static_cast<EOS *>(data); // very "traditional"
return e->functorForT_PS(f);
}
Usage: T1 = OneDimBisector(EOSBisect, this, Tmin, Tmax, tol);
Upvotes: 3
Reputation: 523724
You cannot pass a member function pointer as a function pointer, because the latter lacks the context pointer (the this
) to properly invoke the member function pointer.
The general way to solve this (as in the standard C++ library) is to use a template:
template <typename F>
double OneDimBisector(F fun, float a, float b, float tol){
double val;
val = fun(0.5*(b-a));
return val;
}
and pass a function object to it
struct Evaluator
{
EOS* this_;
Evaluator(EOS* this_) : this_(this_) {} // constructor
double operator()(double value) const // call the function
{
return this_->functForT_PS(value);
}
};
T1 = OneDimBisector(Evaluator(this), Tmin, Tmax, tol);
You could also use std::bind1st(std::mem_fun(&EOS::functForT_PS), this)
, but what it does is just the same as the structure above. (BTW, both std::bind1st and std::mem_fun have been deprecated.)
If you don't like templates, you could accept a polymorphic function instead (e.g. using Boost.Function or std::function in C++11), but it will be slower:
double OneDimBisector(const boost::function<double(double)>& fun,
float a, float b, float tol)
{
return fun(0.5 * (b-a));
}
and finally, if you can use C++11, you could use a lambda function on calling OneDimBisector:
T1 = OneDimBisector([=](double value){ return functForT_PS(value); },
Tmin, Tmax, tol);
Upvotes: 1
Reputation: 1414
The problem you face is that a function pointer is something different to a member funcgtion pointer.
A common (Java World) Approach to circumvent the problem is using the Strategy pattern (fun of the Bisector would be some Implementation of a Strategy).
A common C++-Approach would be using functors/binding, e.g. with boost:
typedef boost::function<double (double)> MyFun;
double OneDimBisector(const MyFun & fun, float a, float b, float tol){
double val;
val = fun(0.5*(b-a)); // actually: do proper bisection
return val;
}
// Calling
T1 = OneDimBisector (boost::bind (&EOS::functForT_PS, *this), Tmin, Tmax, tol));
Upvotes: 0