There's a number of solutions. The one that seems to fit best is Boost.Variant; define your Line
and Circle
classes as you showed, then make GeometricData
a typedef of variant<Line, Circle>
, and you'll be able to store an instance of either one in there. When you want to go back from a GeometricData
to the actual object stored, you can perform a cast, or you can write a so-called visitor. A visitor is just a class specifying an action for each possible type, and then boost::apply_visitor
can be used to select the right action based on what is stored.
Example (using vectors for simpler notation):
struct Line {
Vector3d startPoint, endPoint;
};
struct Circle {
Vector3d center;
float radius;
};
using GeometricData = boost::variant<Line, Circle>;
struct MidpointVisitor : boost::static_visitor<Vector3d> const {
Vector3d operator()(Line const& line) {
return (line.startPoint + line.endPoint)/2;
}
Vector3d operator()(Circle const& circle) const {
return circle.center;
}
};
void foo() {
GeometricData data;
// ...
auto midpoint = boost::apply_visitor(MidpointVisitor{}, data);
// ...
}
A less type-strict solution is Boost.Any, but I don't see any advantages for this case. Even if you did need another option, you'd probably want to specify that explicitly.
I suspect your solution using void*
(or using a common base class and RTTI) could be made to work using smart pointers. However, the only advantages I can see are faster compilation and less awful compiler error messages, while you end up having to bother with dynamic allocation and can't have visitors.
You could also roll your own union for this, effectively implementing something along the lines of Variant. That would involve making sure you get construction, destruction and alignment all correct, and don't trigger some obscure case of undefined behaviour. If that's not a problem for you and you really don't want to use a library, it's an option, but it is very much reinventing the wheel.