I'm writing a C++ library for optimization, and I've encountered a curious issue with contra-variant types.
So, I define a hierarchy of "functions", based on what information they can compute.
class Function {
public:
double value()=0;
}
class DifferentiableFunction : public Function {
public:
const double* gradient()=0;
}
class TwiceDifferentiableFunction : public DifferentiableFunction {
public:
const double* hessian()=0;
}
Which is all well and good, but now I want to define interfaces for the optimizers. For example, some optimizers require gradient information, or hessian information in order to optimize, and some don't. So the types of the optimizers are contravariant to the types of the functions.
class HessianOptimizer {
public:
set_function(TwiceDifferentiableFunction* f)=0;
}
class GradientOptimizer : public HessianOptimizer {
public:
set_function(DifferentiableFunction* f)=0;
}
class Optimizer: public GradientOptimizer {
public:
set_function(TwiceDifferentiableFunction* f)=0;
}
Which I suppose makes sense from a type theoretic perspective, but the thing that is weird about it is that usually when people want to extend code, they will inherit the already existing classes. So for example, if someone else was using this library, and they wanted to create a new type of optimizer that requires more information than the hessian, they might create a class like
class ThriceDifferentiableFunction: public TwiceDifferentiableFunction }
public:
const double* thirdderivative()=0;
}
But then to create the corresponding optimizer class, we would have to make HessianOptimizer extend ThirdOrderOptimizer. But the library user would have to modify the library to do so! So while we can add on the ThriceDifferentiableFunction without having to modify the library, it seems like the contravariant types lose this property. This seems to just be an artifact of the fact the classes declare their parent types rather than their children types.
But how are you supposed to deal with this? Is there any way to do it nicely?
Since they're just interfaces, you don't have to be afraid of multiple inheritance with them. Why not make the optimiser types siblings instead of descendants?
class OptimizerBase
{
// Common stuff goes here
};
class HessianOptimizer : virtual public OptimizerBase {
public:
virtual set_function(TwiceDifferentiableFunction* f)=0;
}
class GradientOptimizer : virtual public OptimizerBase {
public:
virtual set_function(DifferentiableFunction* f)=0;
}
class Optimizer : virtual public OptimizerBase {
public:
virtual set_function(TwiceDifferentiableFunction* f)=0;
}
// impl
class MyGradientOptimizer : virtual public GradientOptimizer, virtual public HessianOptimizer
{
// ...
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With