0

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.

2
  • You can't pass a pointer to a member function as a pointer to a regular function. Can you change OneDimBisector to take different arguments? Commented Jan 13, 2012 at 15:07
  • @VaughCato I can change it but it should stay general---I do not want many copies of the root-finder in the code as it gets used in different classes! Commented Jan 13, 2012 at 19:18

3 Answers 3

3

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);

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your suggestions. Including both functional and stdlib, I get error: 'function' is not a member of 'std', 'bind' is not a member of 'std', etc.. As to the second (I did not know about templates!), does this mean there would be a new instance of the EOS class every time I call T_PS()? This must be avoided because of the overhead (reading in data, etc.). What do you mean by "duplicate code"? Finally, could you please elaborate on the "traditional" solution? This might be the obvious one about which I did not know.
This is new, so you may need to say -std=c++0x in GCC, or otherwise use <tr1/functional> and std::tr1::function etc. A new template instance would be generated for every new type of function you pass. It's not overhead, only code bloat.
I added an example of a "traditional" callback. Note the additional void* for contextual data.
1

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);

4 Comments

Couldn't you boost::bind this to the member function as an additional alternative that doesn't require writing a functor or C++11?
@KennyTM Thanks for your detailed and helpful answer! I like the template solution and it works well for me.
@KennyTM Actually, what should I be doing to compile this if I use multiple files? I have Bisector.h with your first block of code; EOS.h, which has the Evaluator struct nested in the EOS class declaration, #includes Bisector.h; yet in EOS.cpp, I get undefined reference to double Bisector<EOS::Evaluator>(EOS::Evaluator, double, double, double)`, from the linker. Thanks in advance, also if you can look at the extra question (edited).
@Labbedudl: The whole template should be put in the header file.
0

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));                                                                                                                                                                                      

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.