Search code examples
c++bisonunique-ptrderived-classbase-class

Cast to derived class without unique_ptr


Background

I am creating a Cpp parser using Bison and Flex and I stumbled upon a problem: In my parser, I require a vector of base class objects, let's say shapes. Depending on which derived class an object has, I need to access different members. Thus, my intend was to store unique_ptr in the vector. Then upon access, I could dynamically cast them to their derived type.

The Problem

However, I can't get Bison to handle the unique_ptrs correctly. No matter how I specify the parser.yy file, upon compilation I run into errors like

usr/include/c++/9/bits/stl_uninitialized.h:127:72: error: static assertion failed: result type must be constructible from value type of input range

According to this post Bison does not handle unique_ptrs well (as far as I understand Bison does not use std::move() internally) and I assume that this is still the case.

My Question

As I would like to keep the class hierarchy as it is and don't want to fix bugs within Bison itself: Is there an alternative to using unique_ptrs when casting from Base class to Derived class?

Code Example

In particular, I want something like the following to work without using unique_ptrs.

enum Shape_type{
    SHAPE_TYPE_CIRCLE,
    SHAPE_TYPE_TRIANGLE,
};

class Shape{
public:
    enum Shape_type type;
    Shape(enum Shape_type type){ this->type=type; }
    virtual ~Shape(){}
};

class Circle: public Shape{
    int r;
public:
    int get_r(){ return this->r; }
    Circle(int r):Shape(SHAPE_TYPE_CIRCLE){ this->r=r; }
};

int main(void){
    std::vector<std::unique_ptr<Shape>> shapes;
    std::unique_ptr<Shape> circle_ptr = std::make_unique<Circle>(42);
    shapes.push_back(std::move(circle_ptr));
    
    for(auto& s_ptr: shapes){
        switch(s_ptr->type){
        case SHAPE_TYPE_CIRCLE:
        {
            auto c = dynamic_cast<Circle&>(*s_ptr);
            std::cout << "circle with r=" << c.get_r() << std::endl;
            break;
        }
        default: {
            std::cout << "other shape" << std::endl;
            break;
        }
        }
    }
    
    return 0;
}

Any help is greately appreciated. Thanks in advance.


Solution

  • The polymorphic way would be (replacing non-copiable std::unique_ptr by std::shared_ptr):

    class Shape{
    public:
        virtual ~Shape() = default;
        virtual void draw() const = 0;
    };
    
    class Circle: public Shape
    {
        int r;
    public:
        explicit Circle(int r): r(r) {}
        int get_r() const { return r; }
    
        void draw() const override { std::cout << "circle with r=" << r << std::endl; }
    };
    
    class Square: public Shape
    {
        int side;
    public:
        explicit Square(int side): side(side) {}
        int get_side() const { return side; }
    
        void draw() const override { std::cout << "square with side=" << side << std::endl; }
    };
    
    int main()
    {
        std::vector<std::shared_ptr<Shape>> shapes { std::make_shared<Circle>(42) };
        
        for (const auto& shape_ptr: shapes)
        {
            shape_ptr->draw();
        }
        return 0;
    }
    

    With std::variant, you might do

    class Circle
    {
        int r;
    public:
        explicit Circle(int r): r(r) {}
        int get_r() const { return r; }
    
        void draw() const { std::cout << "circle with r=" << r << std::endl; }
    };
    
    class Square
    {
        int side;
    public:
        explicit Square(int side): side(side) {}
        int get_side() const { return side; }
    
        void draw() const { std::cout << "square with side=" << side << std::endl; }
    };
    
    using Shape = std::variant<Circle, Square>;
    
    int main()
    {
        std::vector<Shape> shapes { Circle(42) };
        
        for (const auto& shape: shapes)
        {
            std::visit([](const auto& shape){ shape.draw(); }, shape);
        }
        return 0;
    }