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
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.
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments