This is the mail archive of the gsl-discuss@sources.redhat.com mailing list for the GSL project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: C++ wrapping


On Fri, 2004-04-09 at 15:36, eknecronzontas wrote:
> 
>         First, I wouldn't want the user who wants to
> solve some equation defined by a member function
> double class::func(double x) to have to inherit from a
> solver class. The reason is as follows: what if the
> same class has another member function double
> class:func2(double y) which the user wants to
> integrate. Then the user has to inherit from both a
> solver class and an integrator class. That sucks
> because then multiple inheritance
> comes into play. One could separate the functions
> class::func() and class::func2() from the original
> class, but if they have to communicate with each other
> then things can get ugly

I have been thinking about this sort of thing for a long time,
with an eye toward a future (so far mythical) GSL++.
Here are a few comments.

Yes, inheritance seems like the natural way at first
(although you would inherit from a "system" or "problem"
class rather than a solver class, a minor point). But
you are right; in the final analysis, there is too much
lossage with an inheritance paradigm. Inheritance is
often not a good way to model 'usage'; it is better to have
a design that supports natural usage relationships rather
than to fall back on inheritance. Another way to say it
is that inheritance implies a framework design, which
is ok for driving coarse-grained high-level algorithms,
but tends to be too narrow when you need to vary
the problem in more than one way.

The next best way might be to use a concept-based design,
where a class needs only to conform to some rules about
what you can do with it (ala STL and STL algorithms.)
Then you pay the price in having a very template-heavy
design in C++. Seems to work well for linear algebra libraries
(ala Blitz, MTL, etc). But as far as I can tell, nobody
has figured out how to make it work in a broader setting,
including "function" objects, with algorithms including
minimization, root-finding, integration, etc.

In either case you have to allow for general user-defined
"function" objects; that's the only way the user can get
the kind of parameter encapsulation that he needs. That's
why inheritance seems reasonable at first.

> In order to make a generic solver class, then,
> one needs some way to specify the function to solve. A
> function pointer would work, i.e.   
> solver::solve(double (*func)(double x)), but then the
> member function has to be static. That also sucks,
> because static functions cannot be virtual.

Right, function pointers suck. No way to get the needed
state encapsulation with static functions, and although
you can do pointers to member functions, it's disgusting.


> So now I'm left to some sort of template
> solution. I am presently a variant of the following
> approach (a functor, I guess):
> 
> class function {
>   double operator()(double x);
> };
>   
> template <class T> class function_memb_fptr : public
> function {
>   function_memb_fptr(T *tp, double (T::*fp)(double)) {
>     tptr=tp;
>     fptr=fp;
>   }
>   double operator()(double x) { return
> (*tptr.*fptr)(x); };
> }
>  
> class solver {
>         solve(double x, function f);
> };

There are lots of issues here. For instance, I get nervous when
I see 'tptr' because there is a problem with ownership/lifetime.
The above would have to be replaced with some kind of handle
implementation, and suddenly it all gets much more complicated
than you would like. It is much better if the solver can deal
directly with T::f(x) rather than have to deal through a
reference that needs to be managed. I think you did it this
way in order to get independence from the underlying member
name, basically using the 'function' class to map the underlying
member to operator() in all cases. This kind of name independence
is important, but maybe it can be achieved in safer ways.

A small point, which may turn out to be important, is that using
pointers means you can never benefit from inlining or other
optimization for small functions... probably this worry is
just a premature optimization, but it bothers me since the
user's function is always the thing that is down
at the bottom of the loop.

Finally, I don't think it will work as it stands. The 'solver'
class is not well-defined because 'function' is a class template,
so the 'solve' function or the whole 'solver' would have to be a
template, either on <class T> or on <class FUNCTION>. Either way
you get this situation where the need for the templates bubbles
all the way up. I find this bubbling templates business disgusting.

The problem is that C++ can never allow you to forget where
the function pointer came from; even if you give up and try
to force everything through void *, you still have to remember
the type so you can make the right cast at the end. It's enough
to make you cry. When thinking about this, I wondered if you
could just cast to (double (*func)(double)), forgetting
about where it came from. It took a little massaging to
get gcc to compile the attached file; but no amount of
massaging could get a result other than seg-fault; this is
not surprising.

One consequence of template bubbling which I've never
learned how to avoid, is that you end up with some
nontrivial code in template functions; these
always make me nervous because they open the door to bloat
problems and questions of repository implementation across
different compilers. If anybody knows a definitive answer
to this question, I would be interested to see it. As an aside,
it appears that the 'export' keyword is _not_ the answer;
see various discussions on the web, starting with a "Guru
of the Week" column entitled "Why 'export' is not the answer",
or something like that.


> So is there a better way? I'd love to hear any ideas.

I have never found a good way to do this. I think it is a hard
problem in C++. On the other hand, there may be some template
gymnastics that solves all the problems... who knows.

Assuming that these questions will someday be solved, I bet
they will not be solved in C++. It is more likely that they
will be solved by the right combination of underlying
implementation (C/C++/whatever) with a high-level
environment (python/lisp?/whatever).


Well. Thanks for the message. I would like to see more
discussions about these problems. They seem hard to me.

-- 
Gerard Jungman <jungman@lanl.gov>
Los Alamos National Laboratory
#include <iostream>


class thing
{
public:
  thing(double y) : _y(y) {  }
  double foo(double x);
private:
  double _y;
};

double thing::foo(double x)
{
  return x * _y;
}



int main()
{  
  typedef  double (*dd_func_type)(double);
  typedef  double (thing::*thing_dd_func_type)(double);

  thing t(7.0);

  thing_dd_func_type fp = &thing::foo;

  void * fprc = reinterpret_cast<void *>(t.*fp);
  // dd_func_type fpdd = reinterpret_cast<dd_func_type>(fprc); ERROR
  dd_func_type fpdd = (dd_func_type)(fprc); // compiles...

  std::cout << (t.*fp)(5.0) << "\n";
  std::cout << fpdd(5.0) << "\n";

  return 0;
}

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]