2

I would like to create a function-object class that numerically integrates some functions on-demand. Its public interface would consist of a method void T::step(double dt) that computes an integration step (and updates the internal state of the function-object with the new values), and getters to the current calculated values.

While I'm sure this could be done with pure stand-alone functions, the functions I'll be integrating have many intermediate calculation steps in common, and depend on many internal parameters which must be updated on every step (and are largely irrelevant to the user), so I thought of using a function-object to encapsulate this mechanism.

class myIntegrals
{
public:
  void step(double dt);
  double get_f1_integral() { return f1_integral; };
  double get_f2_integral() { return f2_integral; };

private:
  // funcs to be integrated (n.b. they share parameter/return types)
  double f1(double t); // depends on the state of member attributes;
  double f2(double t); // idem, and also on the results of f1

  double t;
  double f1_integral;
  double f2_integral;
  // and many internal parameters, updated on every step
}

void myIntegrals::step(double dt)
{
  // integrate `f1` from `t` to `t+dt`
  // process results, update internal parameters

  // integrate `f2` from `t` to `t+dt`, update internal parameters
  // process results, update internal parameters

  t += dt;
}

But now I've found myself wrestling the type-system, trying to define a function double integrate(double t, double dt, METHOD f) to actually compute each individual function's integration step, accepting member functions as arguments.

I've come up with

template <typename T>
double integrate(double t, double dt, double (T::*f)(double), T* obj)
{
  // Here I can reference `obj->*f(...)`
}

but I'm a bit uneasy, as it seems contrived and some people make it seem like function pointers are the devil when it comes to performance.

Using only C++11 features, is there a cleaner, more performant or more idiomatic approach to this?

3
  • 1
    It isn't quite clear what you have in mind. Do you envision many member functions with the same signature? Or the only one you ever plan to pass to integrate is step? Commented May 12, 2023 at 18:43
  • Many member functions with the same signature, yes. I've written a simplified version of the code in the question for clarification. Commented May 12, 2023 at 20:00
  • If you always pass constant mem-fun pointers to integrate (i.e. literally &myIntegrals::f1 and &myIntegrals::f2 rather than some variables of that type), you can make performance improvements by making the function pointer a non-type template parameter rather than a regular function parameter: template <typename T, double (T::*f)(double)>. The downside is that in C++11 you need to pass T explicitly: integrate<myIntegrals, &myIntegrals::f2>(...) In modern C++ you could just write template <auto f>. Commented May 13, 2023 at 4:22

1 Answer 1

7

Personally I would offload the call to the member function to the call site by having integrate take a Callable. That forces you to use a lambda expression at the call site or a custom functor. Something like:

template <typename Callable>
double integrate(double t, double dt, Callable c)
{
    return c(t * dt);
}

double step(double) 
{ 
    return 42.0; 
}

struct MyCustomType {
    double step(double) 
    { 
        return 42.0; 
    }
};

int main()
{
    MyCustomType foo;
    // use member function via a lambda wrapper
    integrate(1, 2, [&](double param) { return foo.step(param); });
    
    // use regular function
    integrate(1, 2, step);
}
Sign up to request clarification or add additional context in comments.

2 Comments

I see. Looks practical, albeit a bit hackish, but perhaps there's not much to be done if C++ wasn't designed with functions as first-class citizens in mind. Would you mind commenting on the performance aspects of your solution, as compared to mine?
@impresso In general the lambda can be better as the compiler can inline the function call. It can't do that with the function pointer. That said, it might not do that so you have to profile to know for sure.

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.