Search code examples
c++multiple-inheritancepointer-to-member

Pointer-to-member-function and multiple inheritance


A class Base, which I have no control over, has a function that accepts a member pointer to any class function. It is meant to be used as follows:

class Derived : public Base {
  void bindProperties() {
    Base::bindProperty("answer", &Derived::getAnswer);
  }

  int getAnswer() const { return 42; }
};

Some way (that I neither know nor care about), Base stores this pointer and later allows me to call Derived::get("answer") (of course, this is a simplified situation).

The down side is, that we tried to be smart in the past, and used multiple inheritance:

class ICalculator {
  virtual int getAnswer() const;
};

template<class T>
class LifeAndUniverseCalculator : public T, public ICalculator {
  virtual int getAnswer() const /* override */ { return 42; }

  void bindProperties() {
    T::bindProperty("answer", &ICalculator::getAnswer);  // (*)
  }
};

thinking that the multiple inheritance is not bad, as long as we only use it to inherit an interface and only have one "concrete" base class.

The templating is because sometimes we want to derive from Base and sometimes from one of its derived classes (which I also don't have access to) - if that is irrelevant you can pretend I wrote Base instead of T and drop the template.

Anyway, the problem I am having now, is that when I call

LifeAndUniverseCalculator calc;
calc.bindProperties();
int answer = calc.get("answer");

I get gibberish. I figured it may be something with pointers into vtables, so I tried replacing

    T::bindProperty("answer", &ICalculator::getAnswer);

by

    T::bindProperty("answer", &LifeAndUniverseCalculator::getAnswer);

hoping that it would calculate the offset correctly, but clearly that does not work (as you have figured out by now, I am really second guessing how this all works).

I thought of some options, such as

  • getting rid of the multiple inheritance and putting everything in ICalculator directly in LifeAndUniverseCalculator (it's the only derived class)

  • creating wrapper functions for all ICalculator stuff in LifeAndUniverseCalculator, e.g. LifeAndUniverseCalculator::Calculator_GetAnswer just calls ICalculator::GetAnswer.

I'd like to know

  • Preferably, is there a way to fix the line marked with (*) in a simple way?
  • If not, what is the best solution (one of the alternatives above, or something else)?
  • If I were able to contact the author of class Base and they would be willing and able to change their class, what specifically would I need to ask, if you are able to say something sensible based on my description.

If you need a MCVE, there is one which I think captures the problem on IDEOne.


Solution

  • In your MCVE, the function A::bindFunction (analogous to Base::bindProperty in your simplified code) force casts a member of function of B to a member function of A. This strikes me as the root problem. This can be fixed by changing the type of A::f to be an std::function<int(void)>:

    class A
      : public ABase {
    public:
        // int a, b;
    
        class Unknown{};
        typedef int(A::*Function)();
    
        template<typename T, typename Func>
        void bindFunction(T* owner, Func myf) { 
            f = std::bind(myf,owner);
        }
    
        int call() {
            return f();
        }
    
        //Function f;
        std::function<int(void)> f;
    };
    
    ...
    
    class Combined
     : public A, public B {
    public:     
        Combined(int value) : B(value), A() {}
    
        virtual void /*A::*/bind() /* override */ {
            A::bindFunction( this, &Combined::getValue );
        }
    };
    

    With only this change, your MCVE works, printing out

    The answer to Life, The Universe and Everything is 42
    

    However, I recognize that the code that I changed belongs to a class that you've explicitly mentioned that you cannot modify. Is this indeed what Base does -- it casts member functions of other classes to member functions of itself? (Or perhaps, while my fix makes the code work, I've misidentified the problem).