Search code examples
c++argument-passingpointer-to-member

Passing member function pointer to class-less function


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.


Solution

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