Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Design: Overloading/Overriding many many functions, way to clean up?

The case I am trying to implement here is a Base class which has a function (let's call it modify_command) which can accept many different types virtually so derived classes can implement the modify_command function as they see fit. Right now I have something along these lines in the base class:

class Base 
{
    template<typename Command>
    void modify_command(Command cmd)
    { 
        std::cout << "Modify command called with unimplemented command type:" << typeid(cmd).name();
    }

    virtual void modify_command(SpecificCommandA cmd)
    {
         modify_command<SpecificCommandA>(cmd); // Calls the templated function
    }

    virtual void modify_command(SpecificCommandB cmd)
    {
         modify_command<SpecificCommandB>(cmd); // Calls the templated function
    }

    // etc.
};

Then in a derived class:

class Derived : public Base
{
    virtual void modify_command(SpecificCommandA cmd)
    {
        cmd.x = 1;
        cmd.y = 2;
    }
}

Obviously a virtual template function isn't a possibility so in some form I am going to have to list function declarations for umpteen many argument possibilities which is sure to clutter the class definition and may make it difficult (over time) for additional command types to be handled

The purpose of having the templated function is for this case to compile without needing definition of modify_command(SpecificCommandC) to log an error:

Base * base = new Derived();
SpecificCommandA a;
SpecificCommandB b;
SpecificCommandC c;
base->modify_command(a); //Set's x and y
base->modify_command(b); //Outputs that command type is unimplemented
base->modify_command(c); //Outputs that command type is unimplemented

I really hate how I have this working, does anyone have a suggestion on how this can be cleaned up / reimplemented? The number of commands will continue to grow as the software matures, so extensibility is a must.

Edit: Grammar

like image 208
mascoj Avatar asked Oct 31 '22 02:10

mascoj


2 Answers

Unfortunately, what would solve your problem is a virtual template method, that is not possible.

Here is a more C-ish solution brought to the C++ world that can work around the limitation:

#include<unordered_map>
#include<functional>
#include<memory>
#include<iostream>
#include<utility>

struct BaseCommand {
    static int counter;
};

int BaseCommand::counter = 0;

template<class T>
struct Command: BaseCommand {
    static int type() {
        static const int t = ++counter;
        return t;
    }
};

struct SpecificCommand1: Command<SpecificCommand1> {};
struct SpecificCommand2: Command<SpecificCommand2> {};

class Base {
    struct Handler {
        virtual void operator()(BaseCommand &cmd) = 0;
    };

    template<typename T>
    struct THandler: Handler {
        std::function<void(T)> func;
        void operator()(BaseCommand &cmd) override {
            func(static_cast<T&>(cmd));
        }
    };
protected:
    template<typename T>
    void assign(std::function<void(T)> f) {
        auto handler = std::make_unique<THandler<T>>();
        handler->func = f;
        handlers[T::type()] = std::move(handler);
    }

public:
    template<typename Command>
    void modifyCommand(Command cmd) {
        auto it = handlers.find(Command::type());
        if(it == handlers.end()) {
            std::cout << "Modify command called with unimplemented command type: " << Command::type();
        } else {
            auto &h = *(it->second);
            h(cmd);
        }
    }

private:
    std::unordered_map<int, std::unique_ptr<Handler>> handlers;
};

class Derived: public Base {
public:
    Derived() {
        std::function<void(SpecificCommand1)> f =
            [](SpecificCommand1) {
                std::cout << "handler for SpecificCommand1" << std::endl;
            };

        assign(f);
    }
};

int main() {
    Base *b = new Derived;
    b->modifyCommand(SpecificCommand1{});
    b->modifyCommand(SpecificCommand2{});
}

The basic idea is to give a numeric type at runtime to your commands (it can be done with the CRTP idiom - see BaseCommand and the Command template class).
With this value accessible, it's a matter of creating a type-erased handler to deal with the commands for which you want to provide a specific implementation (see assign and Handler/THandler).
Once you have all the pieces correctly set, you have only to design and initialize those handlers in your derived classes. For it can be done using std::function, you can use as an handler a lambda, a public or private member method, a static method and so on.
See the constructor of Derived for further details.

like image 96
skypjack Avatar answered Nov 02 '22 10:11

skypjack


I am not sure whether this is what you want, but I am going ahead with what I have come up with:

A small metaprogram to find a type within a typelist/variadic param list. Needed to identify whether a command is part of implementation or not.

namespace meta {
template <typename... T> struct list{};
template <typename F, typename T> struct has_type;

template <typename F>
struct has_type<F, list<>> {
  using type = typename std::false_type;
  static constexpr bool value = false;
};

template <typename F, typename... T>
struct has_type<F, list<F, T...>> {
  using type = typename std::true_type;
  static constexpr bool value = true;
};

template <typename F, typename H, typename... T>
struct has_type<F, list<H,T...>> {
  using type = typename std::false_type;
  static constexpr bool value =
    std::is_same<F, typename std::decay<H>::type>::value ? true : has_type<F, list<T...>>::value;
};
}

Now Define your commands having a common base class:

struct CommandBase {};

struct CommandA: CommandBase {};
struct CommandB: CommandBase {};
struct SomeCommandType: CommandBase {};
struct CommandC: CommandBase {};

using AvailableCommmands = meta::list<CommandA, CommandB, SomeCommandType>;

The AvailableCommmands type decides for what type not implemented message should come. If a particular coomand type is not present in AvailableCommmands meta::list, then for that type not implemeneted message should be printed.

Rest of the code (base + derived):

class Base
{
public:
  template <typename T>
  void on_modify_command(T cmd) {
    do_on_modify_command(cmd, typename meta::has_type<T, AvailableCommmands>::type());
  }
private:
  virtual void do_on_modify_command(CommandBase&, std::true_type) = 0;
  virtual void do_on_modify_command(CommandBase& b, std::false_type) {
    std::cout << "Not implemented" << std::endl;
  }

};

class Derived: public Base
{
public:
  void do_on_modify_command(CommandBase& cmd, std::true_type) {
    std::cout << "Specialized command implementation" << std::endl;
    impl(*static_cast<SomeCommandType*>(&cmd));
  }
  void impl(SomeCommandType cmd) {
    std::cout << "huh!!" << std::endl;
  }

};

int main() {
  CommandA ca;
  Base* b = new Derived;
  b->on_modify_command(ca);
  CommandC cc;
  b->on_modify_command(cc);

  return 0;
}

The code is understandably messy, I am sure there must be some better way to do it.

like image 29
Arunmu Avatar answered Nov 02 '22 09:11

Arunmu