Since MPI doesn\'t offer binary compatibility, only source compatibility, we\'re forced to ship our solver source code to customers for them to use our solver with their pre
This seems to be an obvious use case for the Bridge Pattern.
In this case the generic interface for MPI is the Implementor. The customer is expected to provide the ConcreteImplementor for their specific MPI instance. Your solver code would be the RefinedAbstraction as the Abstraction provides the bridge to the Implementor.
Abstract_Solver <>--> MPI_Interface
. .
/_\ /_\
| |
Solver MPI_Instance
The customer inherits from MPI_Interface
and implements it against its MPI instance of choice. The implementation is then fed to the solver interface and used by the Abstract_Solver
as it is doing its work.
Thus, you can make MPI_Interface
as type safe as necessary for Abstract_Solver
to get its work done. No void *
is necessary. The implementor ofMPI_Instance
can store whatever implementation specific MPI state it needs within its instantiated object that would be required to fulfill the contract required by the interface. As an example, the comm
argument could be elided from the MPI_Interface
. The interface could just assume a separate comm
would require a separate instance of MPI_Instance
(initialized to a different comm
).
While the Bridge Pattern is object-oriented, this solution is not limited to C++. You can easily specify an abstract interface in C (as seen in this dynamic dispatching example).