Adi - 4 months ago 12

C++ Question

I have base class Shape and some subclasses inherited from it like Circle, Rectangle, AlignedRectangle, ConvexPolygon, ConcavePolygon etc. I want each of such classes to have method

`bool intersect(Shape &other);`

which checks if two shapes intersect. I want to have different implementations of such method for different pairs of classes, because some intersections can be computed much faster than brute-force approach.

I want such method so I can intersect two Shape* pointers without caring about what types are actually inside.

Here is my current approach.

`class Circle;`

class Rect;

class Shape {

public:

Shape() {}

virtual bool intersect(Shape &a) = 0;

virtual bool intersect_circle(Circle &a) = 0;

virtual bool intersect_rect(Rect &a) = 0;

};

class Circle : public Shape {

public:

Circle() {}

virtual bool intersect(Shape &a);

virtual bool intersect_circle(Circle &a);

virtual bool intersect_rect(Rect &a);

};

class Rect : public Shape {

public:

Rect() {}

virtual bool intersect(Shape &a);

virtual bool intersect_circle(Circle &a);

virtual bool intersect_rect(Rect &a);

};

bool Circle::intersect(Shape &a) {

a.intersect_circle(*this);

}

bool Circle::intersect_circle(Circle &a) {

cout << "Circle::intersect_circle" << endl;

}

bool Circle::intersect_rect(Rect &a) {

cout << "Circle::intersect_rect" << endl;

}

bool Rect::intersect(Shape &a) {

a.intersect_rect(*this);

}

bool Rect::intersect_circle(Circle &a) {

cout << "Rect::intersect_circle" << endl;

}

bool Rect::intersect_rect(Rect &a) {

cout << "Rect::intersect_rect" << endl;

}

Is there some other, 'better' way to do it?

It may be fine for intersection of two shapes, but what if I wanted to have method that have two or more arguments that can have any type? This is extension for 3 classes case:

`void Circle::some_stuff(Shape &a, Shape &b) {`

a.some_stuff_circle(*this, b);

}

void OtherClass::some_stuff_circle(Circle &a, Shape &b) {

b.some_stuff_circle_other_class(a, *this);

}

void AnotherClass::some_stuff_circle_other_class(Circle &a, OtherClass &b) {

//do things here

}

There is gonna be so much redundant code. I may be missing something fundamental though.

Answer

You can use a variant type for this. Here's one possible solution using my Polyvar header. It isn't the prettiest nor the most flexible solution, but it does allow you to selectively overload `intersect`

and still have dynamically polymorphic behavior. I think it's as good as you're going to get in C++.

*Note: Polyvar depends on Boost.Variant, if you use it as-is. Feel free to tweak the macros if you want to use std::variant or some other variant implementation.*

```
#include "polyvar.hpp"
#include <iostream>
// Define a variant template with a self-visiting member
// function named `intersect`. We'll use this to emulate a base class
DEFINE_POLYVAR(ShapeVariant, (intersect));
class Circle {
public:
Circle() {}
template<typename T>
bool intersect(T &a) {
std::cout << "Circle to ???\n";
auto your_implementation = false;
return your_implementation;
}
bool intersect(Circle &a) {
std::cout << "Circle to Circle\n";
auto your_implementation = false;
return your_implementation;
}
bool intersect(class Rect &a) {
std::cout << "Circle to Rect\n";
auto your_implementation = false;
return your_implementation;
}
};
class Rect {
public:
Rect(){}
template<typename T>
bool intersect(T &a) {
std::cout << "Rect to ???\n";
auto your_implementation = false;
return your_implementation;
}
bool intersect(Circle &a) {
std::cout << "Rect to Circle\n";
auto your_implementation = false;
return your_implementation;
}
bool intersect(Rect &a) {
std::cout << "Rect to Rect\n";
auto your_implementation = false;
return your_implementation;
}
};
class Triangle {
public:
Triangle(){}
template<typename T>
bool intersect(T &a) {
std::cout << "Triangle to ???\n";
auto your_implementation = false;
return your_implementation;
}
bool intersect(Triangle &a) {
std::cout << "Triangle to Triangle\n";
auto your_implementation = false;
return your_implementation;
}
};
using Shape = ShapeVariant<Circle, Rect, Triangle /*, etc */>;
// Polyvar adds one level of visitation, but we must add another.
bool intersect(Shape& s1, Shape& s2) {
auto visitor = [&s1](auto& s2) {
return s1.intersect(s2);
};
return boost::apply_visitor(visitor, s2);
}
int main () {
Shape s1 = Circle{};
Shape s2 = Rect{};
Shape s3 = Triangle{};
intersect(s1, s2);
intersect(s2, s1);
intersect(s1, s3);
intersect(s3, s2);
intersect(s1, s1);
intersect(s2, s2);
intersect(s3, s3);
}
```

Output:

Circle to Rect

Rect to Circle

Circle to ???

Triangle to ???

Circle to Circle

Rect to Rect

Triangle to Triangle

See also - multiple dispatch:

By the way, don't forget to make your code `const`

-correct.