Alex Rivera | Logout

Method chaining + inheritance don't play well together?

Asked 2009-02-15T17:40:51.873
17

Consider:

// member data omitted for brevity

// assume that "setAngle" needs to be implemented separately
// in Label and Image, and that Button does need to inherit
// Label, rather than, say, contain one (etc)

struct Widget {
    Widget& move(Point newPos) { pos = newPos; return *this; }
};

struct Label : Widget {
    Label& setText(string const& newText) { text = newText; return *this; }
    Label& setAngle(double newAngle) { angle = newAngle; return *this; }
};

struct Button : Label {
    Button& setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
        return *this;
    }
};

int main() {
    Button btn;

    // oops: Widget::setText doesn't exist
    btn.move(Point(0,0)).setText("Hey");

    // oops: calling Label::setAngle rather than Button::setAngle
    btn.setText("Boo").setAngle(.5); 
}

Any techniques to get around these problems?

Example: using template magic to make Button::move return Button& or something.

edit It has become clear that the second problem is solved by making setAngle virtual.

But the first problem remains unsolved in a reasonable fashion!

edit: Well, I guess it's impossible to do properly in C++. Thanks for the efforts anyhow.

Edit
Report

4 Answers

4

Is a Button really a Label? You seem to be violating the Liskov substitution principle. Perhaps you should consider the Decorator pattern to add behaviors to Widgets.

If you insist on the structure as is, you can solve your problem like so:

struct Widget {
    Widget& move(Point newPos) { pos = newPos; return *this; }
    virtual ~Widget();  // defined out-of-line to guarantee vtable
};

struct Label : Widget {
    Label& setText(string const& newText) { text = newText; return *this; }
    virtual Label& setAngle(double newAngle) { angle = newAngle; return *this; }
};

struct Button : Label {
    virtual Label& setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label::setAngle(newAngle);
        return *this;
    }
};

int main() {
    Button btn;

    // Make calls in order from most-specific to least-specific classes
    btn.setText("Hey").move(Point(0,0));

    // If you want polymorphic behavior, use virtual functions.
    // Anything that is allowed to be overridden in subclasses should
    // be virtual.
    btn.setText("Boo").setAngle(.5); 
}
answered 2009-02-15T20:19:25.720
1

I think (I haven't tested it) this will do it using templates:

template<class T> struct TWidget {
    T& move(Point newPos) { pos = newPos; return (T&)*this; }
};

template<class T> struct TLabel : TWidget<T> { ... }

struct Label : TLabel<Label> { ... }

struct Button : TLabel<Button> { ... }

Notes:

  • Any/every base class needs to be a template, with a separate non-template leaf class at the top (contrast the LabelT and Label classes).
  • The cast could be a dynamic_cast if you like.
  • Instead of casting the "return *this", the base class could contain a T& as a data member (the derived class would pass this to the base class' constructor), which would be an extra data member, but which avoids a cast and I think may permit composition instead of or as well as inheritance.
answered 2009-02-15T17:55:08.937
1

This compiles on gcc 4.3.2 and is sort of a mixin pattern.

#include <string>

using namespace std;

struct Point {
    Point() : x(0), y(0) {}
    Point(int x, int y) : x(x), y(y) {}

    int x, y;
};

template <typename T>
struct Widget {
    T& move(Point newPos) {
        pos = newPos;
        return *reinterpret_cast<T *> (this);
    }

    Point pos;
};

template <typename T>
struct Label : Widget<Label<T> > {
    T& setText(string const& newText) {
        text = newText;
        return *reinterpret_cast<T *> (this);
    }
    T& setAngle(double newAngle) {
        angle = newAngle;
        return *reinterpret_cast<T *> (this);
    }

    string text;
    double angle;
};

struct Button : Label<Button> {
    Button& setAngle(double newAngle) {
        backgroundImage.setAngle(newAngle);
        Label<Button>::setAngle(newAngle);
        return *this;
    }

    Label<Button> backgroundImage;
};

int main() {
    Button btn;

    // oops: Widget::setText doesn't exist
    btn.move(Point(0,0)).setText("Hey");

    // oops: calling Label::setAngle rather than Button::setAngle
    btn.setText("Boo").setAngle(0.0); 
}
answered 2009-02-15T17:58:47.087
0

Well, you know it's a Button so you should be able to cast the returned Widget& as a Button& and keep going. It does look a bit ugly though.

Another rather annoying option is to create a wrapper in your Button class for the Widget::move function (and friends). Probably not worth the effort to wrap everything if you have more than a handful of functions though.

answered 2009-02-15T17:45:23.220

Your Answer