ddouglascarr ddouglascarr - 2 months ago 6
C++ Question

Given a set of classes, call the one with matching method parameters

I have 2 or more classes which inherit from a single parent. They all have overloaded

handle
methods, but each class has different parameters for their handle methods.

class CommandHandler {};

class FooCommandHandler : public CommandHandler
{
public:
string handle(const FooCommand& c) { return c.getStringSomehow(); }
};

class BarCommandHandler : public CommandHandler
{
public:
string handle(const BarCommand& c) { return c.getStringSomeOtherWay(); }
string handle(const OtherBarCommand& c) { return c.youGetThePicture(); }
};

FooCommandHandler fooHandler;
BarCommandHandler barHandler;


I want a function that would assess which of the Classes have the right signature and call it. Is this possible?

Ideally, this would be done at compile time, and it would static_assert that there is exactly one match.

Given the signature
template<typename C> string sendCommand(C c)
:

sendCommand<BarCommand>(c)
would call
barHandler.handle(c)


sendCommand<FooCommand>(c)
would call
fooHandler.handle(c)

Answer

Usual function overloading will work for your case. You ask for signature template<typename C> string sendCommand(C c) So template argument is a type of first function argument. Then just define:

string sendCommand(const FooCommand& c) {
    fooHandler.handle(c);
}
string sendCommand(const BarCommand& c) {
    barHandler.handle(c);
}

And call them. You don't even need templates here.

If you have a lot of commands and handlers you can try this:

// We need a way to retrive handler by its type. Tuple is good for that.
std::tuple<FooCommandHandler, BarCommandHandler> handlers;

// Note the return type. If Handler on position handlerIndex does not have proper method, then instantiation will fail and, due to SFINAE, this function will just be ignored.
template<class Command, size_t handlerIndex = 0>
auto sendCommand(const Command& c) -> decltype(std::get<handlerIndex>(handlers).handle(c))
{
    return std::get<handlerIndex>(handlers).handle(c);
}

// Again, SFINAE technique. Compiler will stop search if the template above has been instantiated and will ignore this one. But in other case this template will be used and it will try to call next handler.
template<class Command, size_t handlerIndex>
std::string sendCommand(const Command& c)
{
    return sendCommand<Command, handlerIndex + 1>()(c);
}

Note that you need a sort of registry which holds all the handlers. Here I use std::tuple as such registry.

Comments