0

I am experimenting with polymorphism and boost::variant in c++11

Here is the code

#include <iostream>
#include <boost/variant.hpp>
using namespace std;

class Polygon {
protected:
        int width, height;
public:
        void set_values (int a, int b)
        {
                width=a;
                height=b;
        }
};

class Rectangle: public Polygon {
public:
        Rectangle() {
                std::cout << "ctor rectangle" << std::endl;
        }

        int area()
        {
                return width*height;
        }
};

class Triangle: public Polygon {
public:
        Triangle() {
                std::cout << "ctor triangle" << std::endl;
        }
        int area()
        {
                return width*height/2;
        }
};


int main () {

        Triangle r;
        boost::variant<Rectangle, Triangle> container = r;
        int x = 4;
        int y = 5;
        if (container.type() == typeid(Rectangle)) {
                r.set_values(x,y);
                std::cout << r.area() << std::endl;
        } else if ( container.type() == typeid(Triangle)){
                r.set_values(x,y);
                std::cout << r.area() << std::endl;
        }

        return 0;


}

I am wondering if this is the best way to proceed. There is a repetition in the code (in main() function) where for every type (we get the type at runtime) we execute the same thing, ie set value and print the area.

Is there any better way to do this?

cateof
  • 6,608
  • 25
  • 79
  • 153
  • 2
    Why not add a `virtual int area()` inside `Polygon`? I don't see any need for `variant` here; regular polymorphism would work fine in this example. – 0x5453 Jun 01 '17 at 15:48
  • 3
    Also RTTI is generally a bad idea, and should be unnecessary when using `variant`. You want to use a visitor instead. – 0x5453 Jun 01 '17 at 15:50
  • 1
    If the question is "how to eliminate repetition in the code", the question seems fine. But your final statement says "Is there any better way to do this?", which is too broad/unclear. If you mean "How do I call a method of a `boost::variant`", it is fine too (excluding dupes). Do be clear about your question – Passer By Jun 01 '17 at 16:43
  • C++14 makes this much better. Are you stuck in C++11? – Yakk - Adam Nevraumont Jun 01 '17 at 17:41
  • See [Generating an interface without virtual functions?](https://stackoverflow.com/questions/18859699/generating-an-interface-without-virtual-functions/18859931#18859931) for two approaches. – sehe Jun 01 '17 at 22:02
  • @Yakk C++11 and not C++14, at least for now – cateof Jun 02 '17 at 10:16

2 Answers2

1

This is a helper class for when you want value-type variant based polymorphism.

template<class Base>
struct poly_ptr_t : boost::static_visitor<Base*> {
  template<class T>
  Base* operator()(T& t)const { return std::addressof(t); }

  template<class...Ts>
  Base* operator[](boost::variant<Ts...>& v) const {
    return boost::apply_visitor( *this, v );
  }
  template<class...Ts>
  Base const* operator[](boost::variant<Ts...> const& v) const {
    return boost::apply_visitor( *this, v );
  }
};

Use:

poly_ptr_t<Polygon> as_polygon;
int main() {
  boost::variant<Triangle, Rectangle> u(Triangle{});
  as_polygon[u]->set_values(x,y);
}

Now, area is a bit of a pain. Getting the parent Polygon won't help, because it doesn't have an area.

If we added

virtual int area() = 0;

to Polygon then

std::cout << as_polygon[v]->area();

suddenly works.

The alternative is a bit of a mess in C++11. In C++14 with appropriate boost support, we get:

std::cout << boost::apply_visitor( [](auto& e){return e.area();}, v );

or

boost::apply_visitor( [](auto& e){std::cout << e.area();}, v );

where we use a generic lambda to call area.

Or we can write an area visitor:

struct get_area : boost::static_visitor<int> {
  template<class T>
  int operator()(T& t)const{ return t.area(); }
};

now we can do this:

std::cout << boost::apply_visitor( get_area, v );

In none of these cases do we have the code repetition within main.

Enlico
  • 23,259
  • 6
  • 48
  • 102
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

Don't use if-else constructs.

Take a look at boost. I typed a small and untested example below.

#include "boost/variant.hpp"
#include <iostream>

class my_visitor : public boost::static_visitor<void>
{
public:
    void operator()(Rectangle const & i) const
    {
        // do something here
    }

    void operator()(Triangle const & i) const
    {
        // do something here
    }
};

int main()
{
    boost::variant< Triangle, Rectangle > u(Triangle());
    boost::apply_visitor( my_visitor(), u );
}
user1587451
  • 978
  • 3
  • 15
  • 30
  • Um, that won't build, or at least it shouldn't. Your visitor returns an `int`, binding that to `auto&` shouldn't be allowed. – Yakk - Adam Nevraumont Jun 01 '17 at 17:47
  • It's untested and quick code, it's not perfect but good enough to understand. – user1587451 Jun 02 '17 at 11:39
  • 1
    It looks fundamentally flawed? You cannot return two different types from a variant visitor. And neither converts to int. And it violates const. I'm not sure what you are trying to do, and I can write a variant visitor. There are 9 lines of actual code, and about half of them contain or contribute to compile time errors. – Yakk - Adam Nevraumont Jun 02 '17 at 12:20
  • ok, ok, I've changed it a bit so maybe it's compileable. At least it's more correct. As you wish – user1587451 Jun 02 '17 at 12:44