I do not know if this is worth it. But machinery to do some of what you want can be written.
We start with MessageBase
. It has a private
constructor.
You then tell it to make MessageHelper<T>
to be a friend
class.
MessageHelper
looks like this:
enum MessageType {
TYPE1, // notice no assigment
TYPE2, // values should be consecutive, distinct, and start at `0`
TYPE3, // or things go poorly later on.
NUM_TYPES /* should be last */
};
template<MessageType> struct MessageTag {}; // empty, for overloading
template<MessageType...> struct MessageTags {};
template<MessageType Last, MessageType... List> struct MakeMessageTags:
MakeMessageTags<MessageType(Last-1), MessageType(Last-1), List...>
{};
template<MessageType... List> struct MakeMessageTags<MessageType(0), List...>:
MessageTags<List...>
{};
typedef MessageBase*(*MessageCreatorFunc)(dsStream<byte>&);
// write this somewhere, next to a given type. If you don't, code later will fail to compile
// (yay). You could make a macro to write these:
MessageCreatorFunc MessageCreator( MessageTag<TYPE1> ) {
return []( dsStream<byte>& st )->MessageBase* {
return new MessageType1(st);
};
}
// manual compile time switch:
template<MessageType... List>
MessageBase* CreateMessageFromStream_helper( MessageType idx, dsStream<byte>& st, MessageTags<List...> )
{
static MessageCreatorFunc creator[] = { MessageCreator(MessageTag<List>())... };
return creator[idx]( st );
}
MessageBase* CreateMessageFromStream( dsStream<byte>& st ) {
// stuff, extract MessageType type
MessageBase* msg = CreateMessageFromStream_helper( type, st, MakeMessageTags<MessageType::NUM_TYPES>() );
// continue
}
the effect of the above code is that we automatically build a manual jump table to create our messages.
If nobody writes the MessageCreator( MessageTag<TYPE> )
overload, or it is not visible in the context of the _helper
, the above fails to compile. So this ensures that if you add a new message type, you write the creation code or you break the build. Much better than a switch statement hiding somewhere.
At some spot there must be an association between MessageType
and the C++ type that should be created: the above machinery just makes sure if that association is not set up, we get a compiler error.
You can have a bit more fun and get a better message by, instead of overloading on MessageCreator, you specialize:
template<MessageType TYPE>
void MessageCreator( MessageTag<TYPE> ) {
static_assert( "You have failed to create a MessageCreator for a type" );
}
// specialization:
template<>
MessageCreatorFunc MessageCreator( MessageTag<TYPE1> ) {
return []( dsStream<byte>& st )->MessageBase* {
return new MessageType1(st);
};
}
which is a bit more obtuse, but might generate a better error message. (while template<>
is not required for all cases, as the override also replaces the template
, by the standard at least one such specialization which can compile must exist, or the program is ill formed, no diagnosis required (!?)).