SakoDaemon
SakoDaemon

Reputation: 1003

Call parent constructor with different arguments depending on child constructor argument

Is there a way to call the parent constructor with different arguments depending on the value of an argument that the child constructor has?

I have the following parent class:

class Rectangle
{
public:
    Rectangle(std::string name, glm::vec3 top_left_corner, float height, float width, glm::vec3 color, bool fill);
    ~Rectangle();
    //...
}

And the child class:

class Wall :
    public Rectangle
{
public:
    Wall(std::string name, Position position, float scene_height, float scene_width, float thickness, glm::vec3 color);
    ~Wall();
    //...
}

Where Position is an enum that should dictate what arguments the parent constructor should be called with:

enum Position { UP, DOWN, LEFT, RIGHT };

So basically, I would like to have something like this in the child constructor:

Wall::Wall(std::string name, Position position, float window_height, float window_width, float thickness, glm::vec3 color) {
    switch(position) {
    case UP:
        Rectangle(name, glm::vec3(0, window_height, 0), thickness, window_height, color, true);
        break;
    case DOWN:
        Rectangle(name, glm::vec3(0, thickness, 0), thickness, window_width, color, true);
        break;
    case LEFT:
        Rectangle(name, glm::vec3(0, window_height, 0), window_height, thickness, color, true);
        break;
    case RIGHT:
        Rectangle(name, glm::vec3(0, window_width - thickness, window_height), window_height, thickness, color, true);
        break;
    }
}

But as you know, I have to call the parent constructor first, like:

Wall::Wall(std::string name, Position position, float window_height, float window_width, float thickness, glm::vec3 color)
    : Rectangle(name, glm::vec3(0, window_width - thickness, window_height), window_height, thickness, color, true) {}

And that doesn't give me much leeway. What would a good, object-oriented approach be?

Upvotes: 5

Views: 1004

Answers (4)

JiaHao Xu
JiaHao Xu

Reputation: 2748

I don't know what is a good OOP approach, but I do know how to do this in C++ at compile-time, if this is what you want.

template <class T, T val>
struct constant
{
#if __cplusplus >= 201703L
    constexpr const static inline T value = val;
#else
    constexpr const static T value = val;
#endif
};

class Wall: public Rectangular
{
    Wall(..., constant<Position, Position::UP>, ...):
        Rectangular(...) {}
    // Providing other overload to initialize your base code differently
};

Edit:

Actually, in your case, you can replace the class constant with std::integral_constant for convenience.

Upvotes: 0

Mohammad Ali
Mohammad Ali

Reputation: 574

In the constructor You are sending duplicate data inside parent class top_left_corner can be obtained from other arguments inside parent constructor.

You can change the top_left_corner with your enum and then calculate it inside the constructor.

if you cannot change parent constructor, I think You can use lambda in parent initialization and use same switch case You wrote

Upvotes: -1

Jarod42
Jarod42

Reputation: 217235

Create a factory method:

Rectangle MakeRectangle(const std::string& name,
                        const Position& position,
                        float window_height, float window_width,
                        float thickness,
                        const glm::vec3& color)
{
    switch(position) {
    case UP:
        return Rectangle(name,
                         glm::vec3(0, window_height, 0),
                         thickness,
                         window_height,
                         color,
                         true);
    case DOWN:
        return Rectangle(name,
                         glm::vec3(0, thickness, 0),
                         thickness,
                         window_width,
                         color,
                         true);
    case LEFT:
        return Rectangle(name,
                         glm::vec3(0, window_height, 0),
                         window_height,
                         thickness,
                         color,
                         true);
    case RIGHT:
        return Rectangle(name,
                         glm::vec3(0, window_width - thickness,  window_height),
                         window_height,
                         thickness,
                         color,
                         true);
    }
    throw std::runtime_error("Invalid position");
}

Then

Wall::Wall(std::string name,
           Position position,
           float window_height, float window_width,
           float thickness,
           glm::vec3 color)
: Rectangle(MakeRectangle(name, position, window_height, window_width, thickness, color)){
// ...
}

Upvotes: 5

CygnusX1
CygnusX1

Reputation: 21779

If I read correctly, in all cases you call the same constructor of the parent class, just the arguments are different. In this setting you can "inject" arbitrary code with a help of a function. For example

class Wall {
  private:
  static glm::vec3 top_left_corner(Position position, float window_height, float window_width, float thickness) {
    switch (position) {
      case UP: return glm::vec3(0, window_height, 0);
      case DOWN: return glm::vec3(0, thickness, 0);
      case LEFT: return glm::vec3(0, window_height, 0);
      case RIGHT: return glm::vec3(0, window_width - thickness, window_height);
    }  
  }

  // similary for other arguments

And then you can invoke your constructor like this:

Wall::Wall(std::string name, Position position, float window_height, float window_width, float thickness, glm::vec3 color)
: Rectangle(name,
    top_left_corner(position, window_height, window_width, thikness),
    ....... /* other arguments */
) {}

Upvotes: 2

Related Questions