0

Let's say I have a class hierarchy like this:

class Rectangle {
 private:
  int h_, w_;
 public:
  Rectangle(int h, int w): h_(h), w_(w) {}
};

class Square : public Rectangle {
 private:
  int side_;
 public:
  Square(int s): side_(s) {}
};

Now if I create an instance of Rectangle (Rectangle rect(3,4)) and cast it to Square* (Square* sqr = (Square*)&rect), the code compiles. But there is no way to set the side_ attribute of the Square in this way. When I print the value of sqr->side_ it prints some garbage value. Is there any way to overload the typecasting from base class to parent class? It is possible to overload the typecast for default datatypes. But how to do it for base->derived conversion. I was trying to overload the () operator in dervied class:

class Square {
...
Square* operator ()(Rectangle* rect) {
   return new Square(rect->h_);
}

But using that like this: Square* sqr = (Square*)&rect, results in compilation error.

Can someone point out some way of doing it?

Ricky
  • 635
  • 2
  • 5
  • 20
  • 3
    Creating an object of type `Rectangle` and casting it to a `Square` to access `Square`'s members will lead you to **undefined behavior** since you don't have a *valid* `Square` object. You need to create a `Square` object instead, eg `Square sq(3);`, and its constructor needs to pass the input value to `Rectangle`'s constructor, eg: `Square(int s): Rectangle(s, s), side_(s) {}` – Remy Lebeau Aug 23 '23 at 18:13
  • Are you trying to remove the hierarchy after "class Derived : Base" – Ripi2 Aug 23 '23 at 18:14
  • 3
    C++ simply does not work the way you think it works. You can't change one object to a different object simply by casting a pointer to it. – Sam Varshavchik Aug 23 '23 at 18:27
  • @SamVarshavchik Yeah, but for abstract classes it is possible, right? – Ricky Aug 23 '23 at 18:36
  • @RemyLebeau what if Rectangle is an abstract class? – Ricky Aug 23 '23 at 18:38
  • 1
    @Ricky You cant create an instance of an abstract class, only an instance of a derived class that implements all of the abstract methods. But, you can have a base-class pointer/reference (even if the base is abstract) that points/refers to a derived-class object, eg: `Rectangle *rect = new Square(3);` and `Square sq(3); Rectangle *rect = &sq;` and `Square sq(3); Rectangle &rect = sq;` This is core to making polymorphism work. – Remy Lebeau Aug 23 '23 at 18:40
  • 1
    No, it's not possible, it's not possible to even create an abstract class in the first place, in C++. Do you have [a good C++ textbook](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) that covers core C++ fundamental concepts? – Sam Varshavchik Aug 23 '23 at 18:40
  • 1
    Sidenote: You're walking into one of the horrors of inheritance. Square and rectangle are not similar enough in behaviours for an inheritance relationship. Squares have only one length. Rectangles have two. Here you try to hide the differences with `int side_;`, but all that really accomplishes is wasting storage because `Square` inherits `int h_, w_;` from `Rectangle`, and sewing confusion because if you use `Square` as a `Rectangle`, what is used, `side_` or `w_` and `h_`? – user4581301 Aug 23 '23 at 18:45
  • 1
    Read [Circle–ellipse problem](https://en.wikipedia.org/wiki/Circle%E2%80%93ellipse_problem) for details, and then read [What is an example of the Liskov Substitution Principle?](https://stackoverflow.com/q/56860/4581301) for a way of thinking to help you avoid problems like this. – user4581301 Aug 23 '23 at 18:47
  • Having an abstract base class is irrelevant. The problem in the initial question is that the code takes a pointer to an object of type `Rectangle` and pretends that it points at an object of type `Square`. It doesn't, and the result of using that pointer is that the behavior of your program is undefined. – Pete Becker Aug 23 '23 at 19:09
  • `operator()` is the **function call operator**. It's not a conversion function. To do this conversion, provide a constructor for `Square` that takes a `Rectangle&` and creates an appropriate `Square` object. Note: this is advice on how to solve the immediate technical problem; it is not an endorsement of the (ill-advised) design. – Pete Becker Aug 23 '23 at 19:11
  • @SamVarshavchik I am not saying that we will make an object of abstract class. But if we to it like this: `Rectangle* rect = new Square(5)` and then typecast it, it is still possible, right? – Ricky Aug 24 '23 at 05:12
  • `Rectangle` is not an abstract class; but in this situation this kind of a cast will be valid. – Sam Varshavchik Aug 24 '23 at 12:03

2 Answers2

1

Your derived and base classes don't look related to me. I.e. side_ member variable can take any value independently from w_ and h_ of its base class, these are two independent invariants. If this is intended, I would just separate the classes and make a constructor of Square that takes a Rectangle object and use one of sides of the object to make a Square:

class Rectangle {
private:
    friend class Square;
    int h_, w_;
public:
    Rectangle(int h, int w): h_(h), w_(w) {}
};

class Square {
private:
    int side_;
public:
    Square(const Rectangle& rect): side_{ rect.h_ } {
        assert(rect.h_ == rect.w_);
    }
};

This way you can make a Square out of Rectangle.

If, however, you meant to maintain hierarchy, be advised that you cannot just make an instance of a derived class out of instance of a base class, since object of the base class is missing extra details required for the derived object. Casting pointers here merely cause undefined behavior, because by referring to the members of the derived class with an object of the base class you refer to memory the object doesn't own. For this scenario I would link the classes by using Rectangle representation in both cases, but complementing invariant in Square with extra conditions:

class Rectangle {
protected:
    int m_height, m_width;
public:
    Rectangle(int height, int width): m_height(height), m_width(width) {
        assert((height > 0) && (width > 0));
    }
};

class Square : Rectangle {
public:
    Square(int side): Rectangle{ side, side } {}

    void setSide(int side) {
        assert(side > 0);
        m_height = side;
        m_width = side;
    }
};
The Dreams Wind
  • 8,416
  • 2
  • 19
  • 49
0

First of all the way you defined class Square will not compile because it implicitly calls the default constructor of the parent class Rectangle. The default constructor of Rectangle was not generated and so you either have to define it(1) or change the constructor initializer of the class Square(2) 1.

class Rectangle {
 ...
 public:
  Rectangle() = default;
};
class Square : public Rectangle {
 public:
  Square(int s): Rectangle(s,s) {}
};

The object of type Rectangle remains a Rectangle object even after you down-cast a pointer to it to a Square pointer. Falsely casting a pointer to a base class (that actually points to an instance of the base class) to a pointer of the derived class and then using it will cause undefined behavior. (see code in the last code snippet below after "2. wrong cast")

But in case you actually created a Square object and use a base pointer to refer to it as in:

Rectangle* rect = new Square(5);

And then you need to access some Square specific functionality so you would like to down-cast Rectangle* to Square* then there are 2 ways.

  1. Dynamic casting using

    Square* square = dynamic_cast<*Square>(rect);
    square->square_method(); // perhaps not available in the base class public interface
    

This will not work in our case because it uses run-time type information available only for classes which have an abstract base class (polymorphic classes). To make it work we would have to make one of the methods of Rectangle virtual.

  1. static cast (unsafe cast) This way we down-cast

    Square* square = static_cast<*Square>(rect);
    square->square_method();
    

but the base class (as opposed to the dynamic_cast case) must not be abstract. So no virtual methods. Also we have to be 100% sure that the original rect pointer was pointing to an instance of Square.Otherwise we are just fooling the compiler and we get undefined behavior.

#include <iostream>
#include <vector>

class Rectangle {
 private:
  int h_, w_;
 public:
  Rectangle(int h, int w): h_(h), w_(w) {}
};

class Square : public Rectangle {
 public:
  Square(int s): Rectangle(s,s) {}
  void special_func(){
    std::cout << "special square logic";
  }
};


int main() {
// 1. proper usage
 Rectangle* rect = new Square(5);
 Square* square = static_cast<Square*>(rect);
 square->special_func();
 // rect->special_func(); class Rectangle' has no member named 'special_func'

// 2. wrong cast
Rectangle* actual_rect = new Rectangle(5,10);
 Square* not_square = static_cast<Square*>(rect);
 not_square->special_func(); // seems to work but is actually Undefined Behavior

 delete rect;
 delete actual_rect;
}
Roger Smith
  • 118
  • 1
  • 8