Suppose I wanted to write this entire system with staticly typed integrators.
I would take your on_integrateButton_clicked
, and change it to something like this:
void MainWindow::on_integrateButton_clicked() {
string whichIntegrator = getUserChoice();
runInNewThread( [whichIntegrator,whichFunction,order]() {
struct functor {
FunctionType func;
OrderType order;
functor( FunctionType func_in, OrderType order_in):func(std::move(func_in)), order(std::move(order_in)) {}
template<typename Integrator>
void operator()( Integrator* integrator ) {
// code using integrator here, not a virtual interface to it, an actual instance of the final type
}
};
RunWithChosenIntegrator( whichIntegrator, functor(whichFunction,order) );
} );
}
As you can see, the code seems a bit backwards.
We defer the type selection as long as possible, and at that point we have it call a functor
with a pointer to the integrator. This means that the code that uses the integrator has full type information of the integrator, and isn't dealing with it abstractly.
Usually, runtime polymorphism or type erasure is sufficient for these kinds of problems, however.
Now, RunWithChosenIntegrator
is a bit of a strange beast. It would have a signature lookling like this:
template<typename Functor>
void RunWithChosenIntegrator( std::string const&whichIntegrator, Functor&& func ) {
if (whichIntegrator == "bob") {
BobIntegrator bob;
func( &bob );
} else if (whichIntegrator == "alice" ) {
AliceIntegrator alice;
func( &alice ):
}
}
as you can see, we call func
with a different type of object based on the whichIntegrator
parameter. There are fun ways you can even generate the if
/else if
chains using metaprogramming, but that probably isn't worth learning until you are more comphy with basic template programming.
The Functor
func
needs to be able to accept pointers to any and all of the types we call it with. A simple example might be a func
that just takes a pointer to a base class, and the one I gave above takes a T*
template type.
All of this is only worth doing if either the overhead of runtime polymorphism is too high, or if you actually need to interact with the different integrator classes in non-uniform ways that is difficult or impossible to capture via runtime polymorphism.
I doubt that this is the case here.