Lets say I'm trying to write multiple handlers for multiple message types.
enum MESSAGE_TYPE { TYPE_ZERO, TYPE_ONE, TYPE_TWO, TYPE_THREE, TYPE_FOUR };
One solution might be
void handler_for_type_one(...){ ... }
void handler_for_type_two(...){ ... }
...
switch(message_type){
case TYPE_ONE: handler_for_type_one(); break;
case TYPE_TWO: handler_for_type_two(); break;
...
And yeah, that would work fine. But now I want to add logging that wraps each of the handlers. Let's say a simple printf
at the beginning / end of the handler function (before and after is fine too).
So maybe I do this:
template<MESSAGE_TYPE>
void handler() {
std::printf("[default]");
}
template<> void handler<TYPE_ONE>() {
std::printf("[one]");
}
template<> void handler<TYPE_TWO>() {
std::printf("[two]");
}
template<> void handler<TYPE_THREE>() {
std::printf("[three]");
}
int main()
{
std::printf("== COMPILE-TIME DISPATCH ==\n");
handler<TYPE_ZERO>();
handler<TYPE_ONE>();
handler<TYPE_TWO>();
handler<TYPE_THREE>();
handler<TYPE_FOUR>();
}
And it works how I'd expect:
== COMPILE-TIME DISPATCH == [default][one][two][three][default]
When the message-type is known at compile time, this works great. I don't even need that ugly switch
. But outside of testing I won't know the message type and even if I did, wrap_handler
(for the logging) "erases" that, requiring me to use the switch "map".
void wrap_handler(MESSAGE_TYPE mt) {
std::printf("(before) ");
switch (mt) {
case TYPE_ZERO: handler<TYPE_ZERO>(); break;
case TYPE_ONE: handler<TYPE_ONE>(); break;
case TYPE_TWO: handler<TYPE_TWO>(); break;
case TYPE_THREE: handler<TYPE_THREE>(); break;
//case TYPE_FOUR: handler<TYPE_FOUR>(); break; // Showing "undefined" path
default: std::printf("(undefined)");
}
std::printf(" (after)\n");
}
int main()
{
std::printf("== RUNTIME DISPATCH ==\n");
wrap_handler(TYPE_ZERO);
wrap_handler(TYPE_ONE);
wrap_handler(TYPE_TWO);
wrap_handler(TYPE_THREE);
wrap_handler(TYPE_FOUR);
}
== RUNTIME DISPATCH == (before) [default] (after) (before) [one] (after) (before) [two] (after) (before) [three] (after) (before) (undefined) (after)
My "goals" for the solution are:
The other solution that seems obvious is a virtual method that's overridden in different subclasses, one for each message type, but it doesn't seem like there's a way to "bind" a message type (enum value) to a specific implementation as cleanly as the template specialization above.
Just to round it out, this could be done perfectly with (other languages) decorators:
@handles(MESSAGE_TYPE.TYPE_ZERO)
def handler(...):
...
Any ideas?
One way I'd get rid of the manual switch statements is to use template recursion, as follows. First, we create an integer sequence of your enum class, like so:
enum MESSAGE_TYPE { TYPE_ZERO, TYPE_ONE, TYPE_TWO, TYPE_THREE, TYPE_FOUR };
using message_types = std::integer_sequence<MESSAGE_TYPE, TYPE_ZERO, TYPE_ONE, TYPE_TWO, TYPE_THREE, TYPE_FOUR>;
Second, let's change slightly the handler and make it a class with a static function:
template <MESSAGE_TYPE M>
struct Handler
{
// replace with this whatever your handler needs to do
static void handle(){std::cout << (int)M << std::endl;}
};
// specialise as required
template <>
struct Handler<MESSAGE_TYPE::TYPE_FOUR>
{
static void handle(){std::cout << "This is my last message type" << std::endl;}
};
Now, with these we can easily use template recursion to create a generic switch map:
template <class Sequence>
struct ct_map;
// specialisation to end recusion
template <class T, T Head>
struct ct_map<std::integer_sequence<T, Head>>
{
template <template <T> class F>
static void call(T t)
{
return F<Head>::handle();
}
};
// recursion
template <class T, T Head, T... Tail>
struct ct_map<std::integer_sequence<T, Head, Tail...>>
{
template <template <T> class F>
static void call(T t)
{
if(t == Head) return F<Head>::handle();
else return ct_map<std::integer_sequence<T, Tail...>>::template call<F>(t);
}
};
And use as follows:
int main()
{
ct_map<message_types>::call<Handler>(MESSAGE_TYPE::TYPE_ZERO);
ct_map<message_types>::call<Handler>(MESSAGE_TYPE::TYPE_THREE);
ct_map<message_types>::call<Handler>(MESSAGE_TYPE::TYPE_FOUR);
}
If now, you want to create your wraphandler, you can do this:
template <MESSAGE_TYPE M>
struct WrapHandler
{
static void handle()
{
std::cout << "Before" << std::endl;
Handler<M>::handle();
std::cout << "After" << std::endl;
}
};
int main()
{
ct_map<message_types>::call<WrapHandler>(MESSAGE_TYPE::TYPE_THREE);
}
Live code here