Search code examples
c++oopderived-classvisitor-pattern

Dynamic type matching in method argument


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.


Solution

  • 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


    Live example


    See also - multiple dispatch:


    By the way, don't forget to make your code const-correct.