Search code examples
c++inheritancepointer-to-member

Best approach for casting pointer to method from derived to base class


We have a base class ByteCode which is intended to be generic. Children of ByteCode are expected to write methods of the form:

void m();

The ByteCode class should have a definition for method:

typedef void (ByteCode::*Method)();

In order to execute the bytecode, we have:

void exec() {
  while (true) {
    uint16_t opcode = getOpcode();
    Method m = opcodes[opcode];
    this->*m();
  }
}

Doing this in one class would be no problem. But we have the generic code in the base class, and the derived has the array:

class MyByteCodeEngine : public ByteCode {
private:
  static Method opcodes[65536];

  void m1() {}
  void m2() {}
  void m3() {}
};

Method MyByteCodeEngine ::opcodes[65536] = {
  MyByteCodeEngine::m1,
  MyByteCodeEngine::m2,
  MyByteCodeEngine::m3
}

The problem is that these methods are not base class, they are derived. But the only instance we have is derived, We don't want to incur the overhead of virtual, we just want to cast and make this work, but the compiler is catching every trick. If it would just trust us:

Method MyByteCodeEngine ::opcodes[65536] = {
  (Method)MyByteCodeEngine::m1,
  (Method)MyByteCodeEngine::m2,
  (Method)MyByteCodeEngine::m3
}

We can solve this problem by eliminating the ByteCode class, but this forces us to repeat the code any time we have a bytecode interpreter. Any suggestions on how to fool C++ into accepting this, cleanly?


Solution

  • You can use the Curiously recurring template pattern so that the base class knows about the type of the member function.

    template<class T>
    struct ByteCode {
      typedef void (T::* Method)();
    
      void exec() {
        while (true) {
          uint16_t opcode = getOpcode();
          Method m = T::opcodes[opcode];
          static_cast<T*>(this)->*m();
        }
      }
    };
    
    class MyByteCodeEngine : public ByteCode<MyByteCodeEngine > {
      private:
        static Method opcodes[65536];
    
        void m1() {}
        void m2() {}
        void m3() {}
    };
    
    MyByteCodeEngine::Method MyByteCodeEngine ::opcodes[65536] = {
      &MyByteCodeEngine::m1,
      &MyByteCodeEngine::m2,
      &MyByteCodeEngine::m3
    }