Tom Dudman
Tom Dudman

Reputation: 108

Chaining derived class methods to base class methods

I am writing a library with chainable methods.

class Base {

  protected:
    int stepper=0;

  public:
    Base& baseMethod( void ) {
     Serial.println( stepper );
     return *this; 
    }
    virtual Base& derivedMethod( void ) =0;
    virtual Base& anotherDerivedMethod( void ) =0;
};

class Derived1 : public Base {

  public:
    Derived1& derivedMethod( void ) {
      stepper += 1;
      return *this;
    }

    Derived1& anotherDerivedMethod( void ) {
      stepper -= 1;
      return *this;
    }
};

class Derived2 : public Base {

  public:
    Derived2& derivedMethod( void ) {
      stepper += 2;
      return *this;
    }

    Derived2& anotherDerivedMethod( void ) {
      stepper -= 2;
      return *this;
    }

    Derived2& specialMethod( void ) {
      stepper *= 2;
      return *this;
    }
};

As you can see, baseMethod() returns a reference to a Base class, so I wouldn't expect to be able to then chain derivedMethod() or anotherDerivedMethod() to it, because the Base class should have no access to any derived methods.

However, in my test code (written for Arduino):

Derived1 myDerived1;
Derived2 myDerived2;

void setup() {

  Serial.begin( 9600 );

  // as expected, these work:

  myDerived1.derivedMethod().baseMethod();  // prints 1
  myDerived1.derivedMethod().derivedMethod().baseMethod();  // prints 3
  myDerived1.anotherDerivedMethod().baseMethod();  // prints 2
  myDerived1.anotherDerivedMethod().derivedMethod().baseMethod();   // prints 2

  myDerived2.specialMethod().baseMethod();  // prints 0
  myDerived2.specialMethod().derivedMethod().baseMethod();  // prints 2
  myDerived2.derivedMethod().specialMethod().baseMethod();  // prints 8

  // I wouldn't expect these to work, but I'm glad they do!

  myDerived1.baseMethod().derivedMethod();  // prints 2
  myDerived1.baseMethod().anotherDerivedMethod();  // prints 3

  myDerived2.baseMethod().derivedMethod();  // prints 8
  myDerived2.specialMethod().baseMethod().derivedMethod();  // prints 20

  // and as expected, these don't work:  

  myDerived2.baseMethod().specialMethod();  // prints 22
  myDerived2.baseMethod().derivedMethod().specialMethod();  // prints 24
}

void loop() { }

I can chain derivedMethod() and anotherDerivedMethod() to baseMethod(), but I can't chain specialMethod() to it.

method_chaining.ino:76:27: error: 'class Base' has no member named 'specialMethod'

The only difference I can see is that specialMethod() isn't mentioned in the Base class definition. Is that why it doesn't work?

How can I chain derived class methods to base class methods?

Upvotes: 1

Views: 501

Answers (2)

Jens
Jens

Reputation: 9406

The problem is that baseMethod's return type is Base and it is not overriden in the derived classes. When calling derived2.baseMethod().specialMethod(), the compiler has to check if the class of the returned object, which is of static type Base as declared, contains a member function specialMethod(). This fails, even though the dynamic type contains it, but this is not known at compile time.

To make your example work, you have to override the function in the derived classes the same way as you do for the other member functions. However, all this only works as long as the static type is a derived type, because the compiler only checks this. If you don't intend to call baseFunction polymorphically through a Base pointer, you can you use static polymorphism:

template<typename T>
class Base {
  public:
    T& baseMethod( void ) {
         return *static_cast<T*>(this);
    }

    virtual Base& derivedMethod( void ) =0;
    virtual Base& anotherDerivedMethod( void ) =0;
};

class Derived1 : public Base<Derived1> {

  public:
    Derived1& derivedMethod( void ) {
      return *this;
    }

    Derived1& anotherDerivedMethod( void ) {
      return *this;
    }
};

class Derived2 : public Base<Derived2> {    
  public:
    Derived2& derivedMethod( void ) {
      return *this;
    }

    Derived2& anotherDerivedMethod( void ) {
      return *this;
    }

    Derived2& specialMethod( void ) {
      return *this;
    }
};

The drawback with this is that the derived classes do not share a common Base<T> parent, because Base<Derived1> is a different type thanBase. To have polymorphism, you have use templatesBase` everywhere.

I am not sure if I like the design. Protected members always make me think that this is implementation-sharing by inheritance, which is something you should avoid. I think

Upvotes: 1

Smeeheey
Smeeheey

Reputation: 10316

The whole point of virtual functions is being able to access derived functionality from a base reference or pointer - that is how polymorphism works. A Base reference has an underlying derived as its dynamic type. In fact because your Base is abstract (i.e. has pure virtual methods) its dynamic type cannot be itself (apart from some limited edge cases, such as during destruction) as you generally can't create objects of an abstract type.

For the same reason, calls pure virtual methods have to resolve (apart from again some limited circumstances) to calls to derived classes.

One exception to the above is when you attempt to call a pure virtual method from a destructor, where it is likely cause a runtime crash ('pure virtual method called').

Upvotes: 1

Related Questions