1

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.

Marc
  • 23
  • 5
  • If you identify your objects by `Shape_type` you don't need to sub class your `Shape` class. – vahancho Apr 22 '21 at 08:14
  • You might use `std::shared_ptr` as smart pointer instead of `std::unique_ptr`, the former is copyable and doesn't require `std::move`. – Jarod42 Apr 22 '21 at 08:14
  • 2
    I'm not sure I understand the question? your code works without unique_ptr already: https://godbolt.org/z/cneq8W6dh ? Please show a [mre] with the code that isn't working – Alan Birtles Apr 22 '21 at 08:15
  • `std::variant` is a good alternative if you know the possible types (as it is suggested by `Shape_type`). `using Shape = std::variant;` , and `std::visit` for the dispatch. – Jarod42 Apr 22 '21 at 08:17
  • @vahancho True. But wouldn't that be a bit messy (i.e. not be in the spirit of object oriented programming)? I have a strong C background and this is my first large C++ project. – Marc Apr 22 '21 at 08:18
  • @AlanBirtles it compiles, but as far as I understand, if you remove the ```std::unique_ptr``` then the vector does not own the resource and the additional information of the derived class is lost. I think this would result in a ```std::bad_cast``` exception. – Marc Apr 22 '21 at 08:24
  • @Marc, if you need to make it object oriented and use polymorphism you should not keep runtime type information for objects as `Shape` and `Circle` are already different types. You probably need to make `get_r()` function virtual in `Shape` class and override it in the `Circle`. Calling `s_ptr->get_r()` in main() function will do things. – vahancho Apr 22 '21 at 08:25
  • @Marc, no, `std::unique_ptr` itself doesn't preserve the type information, its just a safer way of using pointers. you can use pointers or references to store polymorphic types, its storing objects that cause [slicing](https://stackoverflow.com/questions/274626/what-is-object-slicing) – Alan Birtles Apr 22 '21 at 08:29
  • @vahancho thanks, that seems like a clean solution to my problem that avoids unnecessary pointer shenanigans. – Marc Apr 22 '21 at 08:41
  • @AlanBirtles ah ok. Thank you, then it seems that I misunderstood that part. – Marc Apr 22 '21 at 08:46
  • @Jarod42 thanks, that seems to work with bison, though i haven't fully tested all jet as I find vahancho's solution to be cleaner. Concerning the ```std::variant```, I will have to do more research as I'm not familiar with it. – Marc Apr 22 '21 at 08:48

1 Answers1

0

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;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302