Search code examples
c++abstract-classpointer-to-membervirtual-inheritanceincompatibletypeerror

Class Hierarchy with down cast function pointers


TL;DR How can I get the Base::Choose to accept Derived::CustomUserFunction as an argument? The error I'm getting, and I do understand why I get it, but I don't know how to resolve it is: Argument of type "Derived::* (...)" is incompatible with parameter of type "Base::* (...)"

#include <iostream>
    class Base {
    public:
        void Base::Choose(void(Base::*FctPtr)()) {
            (this->*FctPtr)();
        }

        virtual void Base::CustomUserFunction() = 0;
};

    class Derived : public Base {
    public:
        virtual void Derived::CustomUserFunction() {
            std::cout << "CustomFunction\n";
        }
 };

    int main()  {
        Derived D;
        D.Choose(&D.CustomUserFunction);
    }

Full version

I'm writing a Discreet Event Simulation Engine (DESEngine) that uses custom User Functions. Since these Custom User Functions are problem specific, I want to be able to call them without knowing their signature name. The engine reads TURN_ON in a text file and looks for the function pointer mapped to the TURN_ON string alias in an unordered_map.

Basically DESEngine would call upon UserEvents::Choose(std::string Name) that would call a function given by it's event name (string).

Everything was working great, but manageability is getting tough since I have to use the engine for lots of different problems (from differential equation solving to simulating simple computers). (And I had all sorts of different problem specific functions inside my UserEvents class)

Because of that, I've decided to make my class UserEvents an abstract class and inherit it for each kind of problem I have, thus, custom user functions would be stored in the derived class, not in the base, but I want the "Choose" method to reside in the base class. How can I call the functions within the derived classes from the base class?

The error I'm getting, and I do understand why I get it, but I don't know how to resolve it is: Argument of type "UserEvents_MVN::* (...)" is incompatible with parameter of type "UserEvents::* (...)"

class UserEvents
{   
    public:
    // Receives an event name and parameters
    struct EventWithParams { std::string Name; std::vector<boost::any> Params; };

    // Lets an outside class (DESEngine) configure which event to run before calling UserEvents::Choose()
    // TODO: Make getters/setters so Events can be passed through a better way
        EventWithParams Event;

        // Select which function(parameters) to call based on the EventWithParams Event (above)
        int UserEvents::Choose();

    protected:
        void UserEvents::UserFunctionPointerMap_AddFunction
        (std::string Alias, void(UserEvents::*FunctionPointer)(const std::vector<boost::any>&));

        virtual void UserEvents::BuildUFPAliasMap() = 0;

    private:

        // Here we have an unordered map that assigns User Function (pointer) to each Key (string or Alias or Event Name)
        std::unordered_map<std::string, void(UserEvents::*)(const std::vector<boost::any>&)> UserFunctionPointerAliasMap;
    };

Here's the implementation of Choose

int UserEvents::Choose()
    {
        try
        {
            (this->*UserFunctionPointerAliasMap.at(Event.Name))(Event.Params);
            return 0;
        }
        catch (const std::out_of_range e)
        {
            throw std::exception::exception("Unknown User Event");
            return -1;
        }
    }

An example of a derived class

#pragma once
#include "..\\UserEvents.h"

class UserEvents_MVN : public UserEvents
{
public:
    UserEvents_MVN();
    ~UserEvents_MVN();

protected:

    void UserEvents_MVN::MEMDUMP_LOAD(const std::vector<boost::any> &Parameters);

    void UserEvents_MVN::MEMDUMP(const std::vector<boost::any> &Parameters);

    void UserEvents_MVN::InstructionDecode(const std::vector<boost::any> &Parameters);


private:

    virtual void UserEvents_MVN::BuildUFPAliasMap();
};

And how I build the index of function pointers (UserFunctionPointerAliasMap)

void UserEvents_MVN::BuildUFPAliasMap()
{
    UserFunctionPointerMap_AddFunction("MEMDUMP_LOAD",  &UserEvents_MVN::MEMDUMP_LOAD );
    UserFunctionPointerMap_AddFunction("MEMDUMP",   &UserEvents_MVN::MEMDUMP );
    UserFunctionPointerMap_AddFunction("DECODE",    &UserEvents_MVN::InstructionDecode);
}

Solution

  • I sought out help in IRC to which someone pointed me to CRTP - Curiously Recurring Template Pattern which allows methods within Base to access members of Derived and that is what I aim for.

    // The Curiously Recurring Template Pattern (CRTP)
    template<class T>
    class Base
    {
        // methods within Base can use template to access members of Derived
    };
    class Derived : public Base<Derived>
    {
        // ...
    };
    

    And they kindly sent me an example

    #include <string>
    #include <unordered_map>
    #include <iostream>
    
    template <typename T>
    class Dispatch
    {
        std::unordered_map<std::string, void (T::*)()> commands;
    
        protected: // used by application type only
        void Add(std::string name, void (T::*fptr)())
        {
            commands[name] = fptr;
        }
    
        public: // becomes part of the public interface for application type
        /* This part is the only reason to use CRTP/inheritance. The application
           type could simply create this method itself and just call functions of
           a Dispatch<App> member to do all the work, but this approach saves us
           from typing that out. */
        void Choose(std::string s)
        {
            auto fptr = commands.at(s);
            auto target = static_cast<T*>(this);
            (target->*fptr)();
        }
    };
    
    class App : public Dispatch<App>
    {
    public:
        App()
        {
            // for demo purposes
            Add("foo", &App::Test);
        }
    
        void Test()
        {
            std::cout << "dispatched!\n";
        }
    };
    
    int main()
    {
        App a;
        a.Choose("foo");
    }