0

I have this problem which I really struggle to even explain(as you can guess by the title) so I'll make it clear by an example

#include <iostream>
using namespace std;

class shape
{
public:
    shape()
    {
    }
};

class triangle : public shape
{
public:
    triangle()
    {
    }
};

class square : public shape
{
public:
    square()
    {
    }
};

class shapeTeller
{
public:
    shapeTeller() {}

    void tellMeWhatShape(square s)
    {
        cout << "Hello, I'm a square\n";
    }

    void tellMeWhatShape(triangle t)
    {
        cout << "Hello, I'm a triangle\n";
    }
    void tellMeWhatShape(shape s)
    {
        cout << "Hello, I'm a generic shape\n";
    }
};

int main()
{
    shape sh;
    triangle tr;
    square sq;
    shape shapeArray[3] = {sh, tr, sq};

    shapeTeller tell;

    for (auto &element : shapeArray)
    {
        tell.tellMeWhatShape(element);
    }
}

this snippet of code prints three times "Hello, I'm a generic shape", while my desired output would be

"Hello, I'm a generic shape"
"Hello, I'm a triangle"
"Hello, I'm a square"

How can i achieve something like that, considering that I want the array to be of the superclass, and I want it to contains various subclasses?
I also want to make it clear that this is a simplified exhample but in the real implementation I can't use parametric polymorphism cause i want the shapeTeller class' methods to do completely different things. Thanks a lot

THZ
  • 1
  • https://www.learncpp.com/cpp-tutorial/virtual-functions/ – Pepijn Kramer Aug 20 '22 at 11:30
  • Note that the statement `shape shapeArray[3] = {sh, tr, sq}; ` suffers from [object slicing](https://stackoverflow.com/questions/274626/what-is-object-slicing). – G.M. Aug 20 '22 at 11:34
  • What G.M. means is that you should store pointers to the baseclass not an array of baseclass types. – Pepijn Kramer Aug 20 '22 at 12:01
  • [What is object slicing?](https://stackoverflow.com/questions/274626/what-is-object-slicing) – BoP Aug 20 '22 at 12:51
  • The problem here is exactly object slicing(I didn't know the term, so thanks) and how to avoid it in a vector that must contain multiple objects of various subtype of a common super class – THZ Aug 20 '22 at 21:06

1 Answers1

0

Note: An array can only store a single type of object. A subtype of the object could be an entirely different size which is not something compatible with the way C++ stores arrays.

You could be using std::variant here to allow every array element to store one of several element types here:

class shapeTeller
{
public:
    shapeTeller() {}

    void tellMeWhatShape(square s)
    {
        std::cout << "Hello, I'm a square\n";
    }

    void tellMeWhatShape(triangle t)
    {
        std::cout << "Hello, I'm a triangle\n";
    }

    void tellMeWhatShape(shape s)
    {
        std::cout << "Hello, I'm a generic shape\n";
    }

    void tellMeWhatShape(std::variant<square, triangle, shape> const& s)
    {
        std::visit([this](auto const& shape)
            {
                tellMeWhatShape(shape);
            },
            s);
    }
};

int main()
{
    shape sh;
    triangle tr;
    square sq;
    std::variant<square, triangle, shape> shapeArray[3] = { sh, tr, sq };

    shapeTeller tell;

    for (auto& element : shapeArray)
    {
        tell.tellMeWhatShape(element);
    }
}

Alternatively dynamically allocate the shapes and implement the visitor pattern:

class shape;
class triangle;
class square;

struct Visitor
{
    virtual void operator()(shape const&) = 0;
    virtual void operator()(triangle const&) = 0;
    virtual void operator()(square const&) = 0;
};

class shape
{
public:
    virtual ~shape() = default;

    shape()
    {
    }

    virtual void Accept(Visitor& v) const
    {
        v(*this);
    }

};

class triangle : public shape
{
public:
    triangle()
    {
    }

    void Accept(Visitor& v) const override
    {
        v(*this);
    }
};

class square : public shape
{
public:
    square()
    {
    }

    void Accept(Visitor& v) const override
    {
        v(*this);
    }
};

class shapeTeller
{
public:
    shapeTeller() {}

    void tellMeWhatShape(square s)
    {
        std::cout << "Hello, I'm a square\n";
    }

    void tellMeWhatShape(triangle t)
    {
        std::cout << "Hello, I'm a triangle\n";
    }

    void tellMeWhatShape(shape s)
    {
        std::cout << "Hello, I'm a generic shape\n";
    }
};

int main()
{
    auto sh = std::make_unique<shape>();
    auto tr = std::make_unique<triangle>();
    auto sq = std::make_unique<square>();
    std::unique_ptr<shape> shapeArray[3] = { std::move(sh), std::move(tr), std::move(sq) };

    shapeTeller tell;

    struct ShapeTellerVisitor : Visitor
    {
        ShapeTellerVisitor(shapeTeller& teller)
            : m_teller(teller)
        {}

        shapeTeller& m_teller;
        virtual void operator()(shape const& s) override
        {
            m_teller.tellMeWhatShape(s);
        }

        virtual void operator()(triangle const& s) override
        {
            m_teller.tellMeWhatShape(s);
        }

        virtual void operator()(square const& s) override
        {
            m_teller.tellMeWhatShape(s);
        }

    };

    ShapeTellerVisitor visitor{ tell };

    for (auto& element : shapeArray)
    {
        element->Accept(visitor);
    }
}

Note: You could implement Visitor with shapeTeller directly.

fabian
  • 80,457
  • 12
  • 86
  • 114
  • The first solution really solved it for me, the only doubt i have is how safe is to assume that cpp17 is a commonly used standard since g++ default is still cpp14? Anyway thank you a lot!!! – THZ Aug 20 '22 at 21:42