I'm writing a networking-related class. My application receives network messages of the form
[uint8_t message id, uint8_t/uint16_t/uint32_t data ...]
My class allows its user to register a callback for a specific message id.
Since there are variety of different messages with different number of different data entries (data entries are restricted to uint8_t, uint16_t and uint32_t), I decided to use C++11's variadic templates to lessen the burden of repeated code.
Here is my pseudo-code of what I want to do (didn't compile it and doubt it compiles)
#include <arpa/inet.h>
#include <stdexcept>
using namespace std;
template<class ...T>
struct MessageHandler {
size_t size;
std::function<void(T...)> callback;
template<class Head, class... Tail>
void parseHelper(uint8_t *data)
{
if (sizeof(Head) == 1) {
uint8_t val;
memcpy(&val, data, sizeof(Head));
// set next unset argument to the value of val
callback = std::bind(callback, val);
data += sizeof(Head);
} else if (sizeof(Head) == 2) {
uint16_t val;
memcpy(&val, data, sizeof(Head));
val = ntohs(val);
// set next unset argument to the value of val
callback = std::bind(callback, val);
data += sizeof(Head);
} else if (sizeof(Head) == 4) {
uint32_t val;
memcpy(&val, data, sizeof(Head));
val = ntohl(val);
// set next unset argument to the value of val
callback = std::bind(callback, val);
data += sizeof(Head);
} else {
throw std::invalid_argument("We support only 1, 2 and 4 byte integers!");
}
// repeat for the rest of arguments
parseHelper<Tail...>(data);
}
template<class ...Empty>
void parseHelper(uint8_t *data)
{
// do nothing, terminating case of recursion
}
template<class ...T>
void parse(utin8_t *data)
{
// parse `data` into T... arguments and bind them into `callback`
parseHelper<T...>(data);
// at this point `callback` has all arguments binded from `data`
// invoke the callback
callback();
}
}
// <message id, callback-holding helper struct>
std::unordered_map<uint8_t, MessageHandler> myMap;
template<class...T>
void dummy(T&&...)
{
// a dummy, does nothing
}
template<class...T>
void addMessageHandler(uint8_t messageId, std::function<void<T... arg>> callback)
{
MessageHandler<arg> mh;
mh.size = 0;
// order of execution is undefined, but we don't care
dummy( (mh.size += sizeof(arg))... );
mh.callback = callback;
myMap[messageId] = mh;
}
void foo(uint16_t a, uint8_t b, uint16_t c, uint32_t d)
{
// do stuff with the parsed message
}
void bar(uint32_t a)
{
// do stuff with the parsed message
}
int main()
{
// register callbacks
addMessageHandler<uint16_t, uint8_t, uint16_t, uint32_t>(0, std::bind(&foo));
addMessageHandler<uint32_t>(1, std::bind(&bar));
...
// get message over the network
uint8_t messageId = some_network_library.read.first_byte();
MessageHandler mh = myMap[messageId];
uint8_t *data = some_network_library.read.bytes(mh.size);
// parses and calls the callback with parsed values
mh.parse(data);
return 0;
}
In main, we register callbacks for message ids, then receive a message over the network, get appropriate MessageHandler, parse data
variable by variable, appending each of them to the callback bind, and when we have binded everything — call the callback.
So, things that concern me:
Is it even possible to have a map (or some other integer-key struct-value based data-structure with approximately constant lookup) where the value is a template struct and you want to store structs of different type in it? (i.e. values stored in the map are not of homogeneous type).
What do I need to make
parse
andparseHelper
functions to work?- I'm not sure if you can append-bind values to std::function like that
- After calling the callback in
parse
, how do I unbind all the bind values? (or they automatically unbind after the call?)
How do I make this code work?
It would be great if someone could fix my pseudo-code into a working one, explaining why my code wouldn't work and how it's fixable, but just explanations are very very helpful too!