Let type Shape be defined as follows:
$variant Shape
{
default Circle(Point(2,1),10);
Point;
Line;
Square;
Rectangle;
Circle;
Polygon;
};
Shape is a value type that is able to hold either a Point, Line, Square, Rectangle, Circle or Polygon value. Putting it another way, the set of all Shape values consists of the set theoretic union over the set of Point, Line, Square, Rectangle, Circle and Polygon values.
See these definitions of the underlying types (i.e. types Point, Line, Square, Rectangle, Circle and Polygon).
In a way Shape is kind of like a retrospectively defined supertype of those 6 models because a value (n.b. not a pointer to a variable!) of any one of them can be implicit cast to a Shape value. E.g.
Point p(0,0);
Shape s = p;
It is important to be aware that the sense in which Circle is-a Shape is very different to the normal OO notion of subtype. The former relates to value substitutability (the set of circle values is a subset of the set of shape values), and the latter relates to pointer to state machine substitutability (aka LSP) which would normally suggest no inheritance amongst value types is possible (e.g. under LSP one would conclude that a circle is not a shape because a shape variable holding a circle value can be reassigned such that it no longer holds a circle). This is relevant to the well known square is-a rectangle debate.
The implementation of the closed variant type is a little tricky because the types to union over can have non-trivial constructors which means that a C++ union cannot be used. When a Shape destructs it has to run the appropriate destructor (e.g. in the case of the Polygon it needs to destruct the vector of points).
The polymorphic Area(Shape) function is automatically written for you using a switch statement on the tag, calling the appropriate Area() function on the data types declared in the variant.
Since this form of polymorphism doesn't involve a vtable, it makes more sense to use free functions than class methods:
float32 Area(const Shape& s);
This is particularly important for multiple dispatch, which makes it even clearer that we don't treat the first argument specially.
The following is a declaration of a dynamic polymorphic function on a variant. Note the square brackets on the argument which is to be treated polymorphically.
$polymorphic float32 Area([const Shape& s]);
This implicitly generates the following code:
float32 Area(Shape const& s)
{
switch(s.GetTag())
{
case 0 : return Area(s.as<Point>());
case 1 : return Area(s.as<Line>());
case 2 : return Area(s.as<Square>());
case 3 : return Area(s.as<Rectangle>());
case 4 : return Area(s.as<Circle>());
case 5 : return Area(s.as<Polygon>());
}
}
We can also pass by value for a polymorphic type. For example:
$polymorphic float32 Perim([Shape s]);
void Offset(Point& p, float32 dx, float32 dy)
{
p.x += dx;
p.y += dy;
}
void Offset(Line& l, float32 dx, float32 dy)
{
Offset(l.p1, dx,dy);
Offset(l.p2, dx,dy);
}
void Offset(Square& s, float32 dx, float32 dy)
{
Offset(s.p1, dx,dy);
}
void Offset(Rectangle& r, float32 dx, float32 dy)
{
Offset(r.p1, dx,dy);
Offset(r.p2, dx,dy);
}
void Offset(Circle& c, float32 dx, float32 dy)
{
Offset(c.c, dx,dy);
}
void Offset(Polygon& p, float32 dx, float32 dy)
{
for (ssize_t i=0 ; i < p.V.size() ; ++i)
{
Offset(p.V[i], dx,dy);
}
}
$polymorphic void Offset([Shape& s], float32 dx, float32 dy);
Multiple dispatch is normally a pain in C++.
To compute the area of intersection between two shapes requires multiple dispatch. There are N2 combinations (in this case 6x6 = 36), which for obvious reasons are not shown here.
float32 AreaOfIntersection(const Point& t1, const Point& t2);
float32 AreaOfIntersection(const Point& t1, const Line& t2);
float32 AreaOfIntersection(const Point& t1, const Square& t2);
float32 AreaOfIntersection(const Point& t1, const Rectangle& t2);
float32 AreaOfIntersection(const Point& t1, const Circle& t2);
float32 AreaOfIntersection(const Point& t1, const Polygon& t2);
float32 AreaOfIntersection(const Line& t1, const Point& t2);
float32 AreaOfIntersection(const Line& t1, const Line& t2);
float32 AreaOfIntersection(const Line& t1, const Square& t2);
float32 AreaOfIntersection(const Line& t1, const Rectangle& t2);
float32 AreaOfIntersection(const Line& t1, const Circle& t2);
float32 AreaOfIntersection(const Line& t1, const Polygon& t2);
...
float32 AreaOfIntersection(const Polygon& t1, const Point& t2);
float32 AreaOfIntersection(const Polygon& t1, const Line& t2);
float32 AreaOfIntersection(const Polygon& t1, const Square& t2);
float32 AreaOfIntersection(const Polygon& t1, const Rectangle& t2);
float32 AreaOfIntersection(const Polygon& t1, const Circle& t2);
float32 AreaOfIntersection(const Polygon& t1, const Polygon& t2);
$polymorphic float32 AreaOfIntersection([const Shape& s1],[const Shape& s2]);