lundi 1 novembre 2021

C++ trying to find a suitable design pattern for a complex problem regarding CRTP and dependency

Let's consider the following example:

(Function<FunctionType> is just a class that applies some function specified by the enum FunctionType)

template<FunctionType funcType>
class System {
public:
    void doEverything() {
        //Iterate through every module in sequence and call doSomething()
    }
    
    void undoEverything() {
        //Iterate through every module in sequence and call undoSomething()
    }

    template<class Derived>
    void add(GenericModule<Derived>& module) {
        //Add module to list
    }
};

template<class Derived>
class GenericModule {
private:
    Derived* crtp = static_cast<Derived*>(this);
    
public:

    void doSomething() {
        crtp->doSomething();
    }
    
    void undoSomething() {
        crtp->undoSomething();
    }
    
};

template<FunctionType funcType, int param>
class ModuleA : public GenericModule<ModuleA<funcType, param>> {
private:
        Function<funcType> func;
public:
        
        void doSomething() {
            std::cout << param+1 << "\n";
        }
        
        void undoSomething() {
            std::cout << func.doFunction(param) << "\n";
        }
};

template<FunctionType funcType, int param1, int param2>
class ModuleB : public GenericModule<ModuleB<funcType, param1, param2>> {
private:
    Function<funcType> func;
public:
    
    void doSomething() {
        std::cout << param1 + param2 << "\n";
    }
    
    void undoSomething() {
        std::cout << func.doFunction(param1+param2) << "\n";
    }
};

int main() {
    System<FUNC1> system;
    ModuleA<FUNC1, 1> m1;
    ModuleB<FUNC1, 1, 2> m2;
    
    system.add(m1);
    system.add(m2);
    system.doEverything();
    system.undoEverything();
}

Goal: The goal with this is to create multiple modules which together make up a "system" so that with only one call to doEverything() or undoEverything() all their respective functions can be executed in sequence. Important is that all modules of a system share the same FunctionType argument specified in the system template. I intentionally used CRTP as this has to be highly efficient and thus any use of virtual functions is discouraged.

My approach was to use CRTP to create a common interface GenericModule from which all modules can be called. I thought the observer pattern might be suitable to register any module in system.

Problems:

  1. How can I implement the add/register functionality in system? I'm thinking about a heterogeneous container that stores any type of GenericModule or something similar. It's important that this technique has as little overhead as possible which is certainly going to be difficult to implement. I'd also consider a complete re-design of this functionality as this might not be optimal.

  2. The FunctionType argument is only relevant to the System class and not to a particular module. The module still needs this argument for it's functionality and due to efficiency reasons it has to be defined as a template argument so it can be used in a constexpr context. But because this argument is already set in the System class and all it's modules share the same value it's highly redundant for it to be redefined on every instantation of a module. I want to instantiate each module in the context of it's system so that the FunctionType argument doesn't have to be redefined every single time. In combination with that I't might even be possible that System automatically adds/registers the module when it's instantiated in it's context.

I have no clue how I could implement all these afromentioned things and that's why I'm asking you.

Update: I just had an idea on how I could solve the first problem. Instead of storing each module in the System class I could just put a reference to the next module as a member variable in the previous module. But I have not yet figured out how I could set it's type at compile time.

Aucun commentaire:

Enregistrer un commentaire