tag dispatching for inherited classes - c++

I have some code where I have a base class (lets call it foo) that has a variable number of derived classes (between 10-500) created by a generation script. Currently we have a function that will create a new base class by passing in its name as a string and then using a giant if/else statement to find the right one.
for example
if (name == "P2_26") {add_module(new P2_26());}
else if (name == "P4_30") {add_module(new P4_30());}
...
This leads to a giant if else block. This seems to me like code that could be simplified by using tag dispatching, but every example I find online uses built-ins like iterators that already have tags defined and I could not interpolate to my use case. Is there anyway to streamline this code?

Tag dispatched is based on type information as an input. Judging from your code you have a string as an input which which can not be used in run time.
Your case looks more like an abstract factory:
// Factory.h
class Base;
struct Factory {
using spawn_t = std::function<Base*()>;
using container_t = std::unordered_map<std::string, spawn_t>;
static container_t& producers() {
// This way it will be initialized before first use
static container_t producers;
return producers;
}
static Base* spawn(const std::string& name) {
auto it = producers().find(name);
if (it == producers().end()) return nullptr;
return it->second();
}
};
// Base.h
#define STR(x) #x
#define DEFINE_REGISTRATOR(_class_) \
DerivedRegistrator<_class_> _class_::_sRegistrator_(STR(_class_))
#define DECLARE_REGISTRATOR(_class_) \
static DerivedRegistrator<_class_> _sRegistrator_
template<typename T>
struct DerivedRegistrator{
DerivedRegistrator(const std::string& name) {
Factory::producers()[name] = [](){ return new T(); };
}
};
class Base {
// ...
};
And then generated files should include:
// Derived1.h
class Derived1 : public Base {
DECLARE_REGISTRATOR(Derived1);
// ...
};
// Derived1.cpp
DEFINE_REGISTRATOR(Derived1); // Will register automatically
This solution will register all classes automatically on program start which is more like what you had before.
UPD.
To use it you can simply replace all your if-else code with this line:
add_module(Factory::spawn(name));
Or if you can't handle nullptr in add_module:
Base* ptr = Factory::spawn(name);
if (ptr) {
add_module(ptr);
}
Thanks to D Drmmr for making this code better.

template<class T>
struct named_factory {
const char* name;
std::function<std::unique_ptr<T>()> factory;
};
struct find_factory {
using is_transparent=std::true_type;
struct named {
const char* str;
template<class T>
named(named_factory<T> const& f):str(f.name) {}
named(const char* name):str(name) {}
};
bool operator()(named lhs, named rhs) {
return strcmp(lhs.str, rhs.str)<0;
}
};
#define MAKE_STR2(X) #X
#define MAKE_STR(X) MAKE_STR2(X)
#define FACTORY(X,...) \
named_factory<__VA_ARGS__>{\
MAKE_STR(X),\
[]{\
return std::make_unique<X>()\
}\
}
Now we can:
std::set<named_factory<foo>, find_factory> factories = {
FACTORY(P2_26, foo),
FACTORY(P4_30, foo),
// ...
};
and in code you do:
bool add_module_by_name( const char* name ) {
auto it = factories.find(name);
if (it == factories.end()) return false;
auto module = it->factory();
if (!module) return false;
add_module( module.release() );
return true;
}
This is a data-driven design. The search for the right type is done in logarithmic time, not linear like your code. You could probably replace it with an unordered_map instead of a set.
However, if your type names are determined at compile time, you can do better. (Ie, if you have a hard coded "P2_26" at the call site).
template<class T>
struct tag_t { using type=T; constexpr tag_t(){} };
template<class T>
constexpr tag_t<T> tag{};
template<class T>
void add_module( tag_t<T> ) {
// ...
add_module( new T() );
}
Now you can add_module(tag<P2_26>) and skip the long if/else statement.
We can even hide the implementation of the outer add_module via this:
// in cpp file:
void add_module_impl( std::function< std::unique_ptr<foo>() > maker ) {
// ...
add_module( maker().release() );
}
// in h file:
void add_module_impl( std::function< std::unique_ptr<foo>() > maker );
template<class T>
void add_module( tag_t<T> t ) {
add_module_impl([]{ return std::make_unique<T>(); });
}
and again, we can add_module(tag<P4_30>) and it just works.

Related

Call function on one of many vectors without enum and switch

I have a class that contains several vectors of unrelated classes.
class Class0 {};
class Class1 {};
class Class2 {};
enum class RemoveFromVector : uint8_t { CLASS0 = 0, CLASS1 = 1, CLASS2 = 2 };
class C
{
public:
std::vector<Class0> c0s;
std::vector<Class1> c1s;
std::vector<Class2> c2s;
void RemoveFromVector(const RemoveFromVector RFV, const int Index)
{
// I'd like to replace this switch with something that's compile-time
switch ((uint8_t)RFV)
{
case 0: c0s.erase(c0s.begin() + Index); break;
case 1: c1s.erase(c1s.begin() + Index); break;
case 2: c2s.erase(c2s.begin() + Index); break;
default: break;
}
}
};
int main()
{
C c;
c.c0s.push_back(Class0());
c.c1s.push_back(Class1());
c.c1s.push_back(Class1());
c.c1s.push_back(Class1());
c.c2s.push_back(Class2());
// this should remove the last element from C.c1s
c.RemoveFromVector(RemoveFromVector::CLASS1, 2);
}
How would I write a function that removes an element from one of the vectors based on an enum (or int) at runtime without having to write a switch that's got a case for every single vector?
In other words, I'm looking for a way to deduce which vector to call erase() on statically at compile-time, which I then call at runtime. The correct term might be "static dispatch" though I'm not entirely certain.
You should keep the code as much as simple as possible. As per the currently shown code, it is simple and readable for every developer who works later on the codebase.
Secondly, the internal storage via std::vectors will make this task anyways at run-time. Because, most of the operations happen with the std::vector is run time overhead as they allocate the memory and manage it at run time. Therefore you can not do any compile-time work for std::vector::erase and whatsoever.
That being said, if you insist to avoid the switch statement, and bring the template complication, one is below, which still would have kind of template type mapping using if constexpr, and the vector erase happens at run-time.
#include <type_traits> // std::is_same_v
class C
{
template<typename ClassType>
auto& getVectorOf() /* noexcept */
{
if constexpr (std::is_same_v<ClassType, Class0>) return c0s;
else if constexpr (std::is_same_v<ClassType, Class1>) return c1s;
else if constexpr (std::is_same_v<ClassType, Class2>) return c2s;
}
public:
std::vector<Class0> c0s; // recommended to be private!
std::vector<Class1> c1s;
std::vector<Class2> c2s;
template<typename ClassType>
void RemoveFromVector(const std::size_t index) /* noexcept */
{
// some index check!
auto& vec = getVectorOf<ClassType>();
vec.erase(vec.begin() + index);
}
};
Call the function like
C c;
// fill the vectors
c.RemoveFromVector<Class0>(0);
c.RemoveFromVector<Class1>(2);
c.RemoveFromVector<Class2>(0);
(See a Demo Online)
Here's a slightly more generic solution:
template <typename... Types>
class MultiStack
{
public:
template <typename T>
/*const*/ T& Get() /*const*/
{
return GetStack<T>().back();
}
template <typename T>
void Push( const T& t )
{
GetStack<T>().push_back( t );
}
template <typename T>
void Pop()
{
GetStack<T>().pop_back();
}
template <size_t... Sizes>
void Reserve()
{
auto reserve = [&]( auto&... stacks ) { ( stacks.reserve( Sizes ), ... ); };
std::apply( reserve, Stacks );
}
private:
template <typename T>
std::vector<T>& GetStack()
{
return std::get<std::vector<T>>( Stacks );
}
std::tuple<std::vector<Types>...> Stacks;
};
Usage looks nice and simple:
using MyStack = MultiStack<Class0,Class1,Class2>;
MyStack stack;
stack.Push( ClassX() ); // automatically pushes any compatible object on the appropriate stack
stack.Pop<Class1>(); // pops/"erases" last object of the Class1 stack (vector)
You could extend this if you need to Pop more than one object at a time, or 'call other functions' on the vector. You could/should also make Get return const T& depending on your needs. (Get() const will need GetStack() const)
I have left in the fancy Reserve() just to show off. ;-) You will probably want to set certain but different initial sizes for your stacks.
I think std::variant and std::visit are the way you can go. You also can use a new helper function make_variant_array to make the code even shorter:
#include <cstdint>
#include <vector>
#include <variant>
#include <array>
#include <functional>
class Class0 {};
class Class1 {};
class Class2 {};
enum class RemoveFromVector
: uint8_t
{
Class0 = 0, Class1 = 1, Class2 = 2
};
template <class... Args>
auto make_variant_array(Args&... args)
{
using var_t = std::variant<std::reference_wrapper<Args>...>;
return std::array<var_t, sizeof...(Args)>{std::ref(args)...};
}
class C
{
public:
std::vector<Class0> c0s;
std::vector<Class1> c1s;
std::vector<Class2> c2s;
void RemoveFromVector(const RemoveFromVector RFV, const int Index)
{
static auto lookup = make_variant_array(c0s, c1s, c2s);
std::visit([Index](auto& vec) { vec.get().erase(vec.get().begin() + Index); }, lookup[(uint8_t)RFV]);
}
};
int main()
{
C c;
c.c0s.push_back(Class0());
c.c1s.push_back(Class1());
c.c1s.push_back(Class1());
c.c1s.push_back(Class1());
c.c2s.push_back(Class2());
// this should remove the last element from C.c1s
c.RemoveFromVector(RemoveFromVector::Class1, 2);
}

using STL smart pointers with COM interfaces

I am trying to using the standard C++ library smart pointers with a library which uses MS COM for most of its function (I must say I am not well-versed with COM). So, I have the following custom deleter for my unique_ptr
struct COMDeleter {
template<typename T> void operator()(T* ptr) {
if (ptr) ptr->Release();
}
};
In the sample code, we have something like:
class MyClass
{
public:
MyClass(IDeckLink * device)
: m_deckLink(device)
{
}
MyClass::~MyClass()
{
if (m_deckLink != NULL)
{
m_deckLink->Release();
m_deckLink = NULL;
}
}
IDeckLink * m_deckLink;
};
This could be replaced with:
class MyClass
{
public:
MyClass(IDeckLink * device)
{
m_deckLink.reset(device);
}
std::unique_ptr<IDeckLink, COMDeleter> m_deckLink;
};
Now, I have another interface called IDeckLinkInput which I would like to wrap in a similar way but the way this is initialized is different as follows:
IDeckLinkInput* m_deckLinkInput = NULL;
if (m_deckLink->QueryInterface(IID_IDeckLinkInput, (void**) &m_deckLinkInput) != S_OK)
return false;
So, if I have a smart-pointer like:
std::unique_ptr<IDeckLinkInput, COMDeleter> m_deckLinkInput(nullptr);
I am not sure how I can use it with initialisation function like the above? Can it even be done or should i just stick to old style C++?
Something like this:
template<class U, class T>
std::unique_ptr<U, COMDeleter>
upComInterface( GUID guid, T const& src ) {
if (!src) return {};
T* r = nullptr;
if (src->QueryInterface( guid, (void**)&r) != S_OK)
return {};
return {r, {}};
}
then we:
auto deckLink = upComInterface<IDeckLinkInput>( IID_IDeckLinkInput, deckLink );
There is a minor DRY violation here -- the link between IDeckLinkInput and IID_IDeckLinkInput has to be repeated each time you do this, and getting it wrong leads to undefined behavior.
We can fix this via a number of mechanisms. Personally, I'd go with a tag dispatch type:
namespace MyComHelpers {
template<class T> struct com_tag_t {using type=T; constexpr com_tag_t(){};};
template<class T> constexpr com_tag_t<T> com_tag{};
template<class T>
constexpr void get_com_guid( com_tag_t<T> ) = delete; // overload this for your particular types
template<class T>
constexpr GUID interface_guid = get_com_guid( com_tag<T> );
}
Now we can associate the type with a guid. In the namespace of IDeckLinkInput do this:
constexpr GUID get_com_guid( MyComHelpers::com_tag_t<IDeckLinkInput> ) {
// constexpr code that returns the GUID
}
we then rewrite the get interface function:
std::unique_ptr<U, COMDeleter>
com_cast( T const& src ) {
if (!src) return {};
T* r = nullptr;
if (src->QueryInterface( MyComHelpers::interface_guid<T>, (void**)&r) != S_OK)
return {};
return {r, {}};
}
and use becomes:
auto declLink = com_cast<IDeckLinkInput>(m_deckLinkInput);
There are many ways to associate the type with the guid, including traits classes. The constexpr ADL-based lookup function and variable template is just one way.
Code not tested.

Choose template based on run-time string in C++

I have an attribute vector that can hold different types:
class base_attribute_vector; // no template args
template<typename T>
class raw_attribute_vector : public base_attribute_vector;
raw_attribute_vector<int> foo;
raw_attribute_vector<std::string> foo;
Based on run-time input for the type, I would like to create the appropriate data structure. Pseudocode:
std::string type("int");
raw_attribute_vector<type> foo;
Obviously, this fails. An easy, but ugly and unmaintainable workaround is a run-time switch/chained if:
base_attribute_vector *foo;
if(type == "int") foo = new raw_attribute_vector<int>;
else if(type == "string") ...
I read about run-time polymorphism with functors, but found it quite complex for a task that is conceptually easy.
What is the best and cleanest way to make this work? I played around with boost::hana, finding that while I can create a mapping from string to type, the lookup can only be done at compile time:
auto types =
hana::make_map(
hana::make_pair(BOOST_HANA_STRING("int"), hana::type_c<int>),
hana::make_pair(BOOST_HANA_STRING("string"), hana::type_c<std::string>)
);
All possible types are known at compile-time. Any suggestions are highly appreciated. In a perfect solution, I would create the name->type mapping in a single place. Afterwards, I would use it like this
std::vector<base_attribute_vector*> foo;
foo.push_back(magic::make_templated<raw_attribute_vector, "int">);
foo.push_back(magic::make_templated<raw_attribute_vector, "std::string">);
foo[0]->insert(123);
foo[1]->insert("bla");
foo[0]->print();
foo[1]->print();
It is not required for this magic to happen at compile time. My goal is to have as readable code as possible.
I'd use an std::map that has strings as key and std::function as values. I would associate the string with a function that returns your type. Here's an example:
using functionType = std::function<std::unique_ptr<base_attribute_vector>()>;
std::map<std::string, functionType> theMap;
theMap.emplace("int", []{ return new raw_attribute_vector<int>; });
theMap.emplace("float", []{ return new raw_attribute_vector<float>; });
// Using the map
auto base_vec = theMap["int"](); // base_vec is an instance of raw_attribute_vector<int>
Of course, this solution is valid if you only know the string value at runtime.
enum class Type
{
Int,
String,
// ...
Unknown
};
Type TypeFromString(const std::string& s)
{
if (s == "int") { return Type::Int; }
if (s == "string") { return Type::String; }
// ...
return Type::Unknown;
}
template <template <typename> class>
struct base_of;
template <template <typename> class C>
using base_of_t = typename base_of<C>::type;
And then the generic factory
template <template <typename> class C>
std::unique_ptr<base_of_t<C>> make_templated(const std::string& typeStr)
{
Type type = TypeFromString(typeStr);
static const std::map<Type, std::function<std::unique_ptr<base_of_t<C>>()>> factory{
{Type::Int, [] { return std::make_unique<C<int>>(); } },
{Type::String, [] { return std::make_unique<C<std::string>>(); } },
// ...
{Type::Unknown, [] { return nullptr; } }
};
return factory.at(type)();
}
a specialization is needed for each base:
template <>
struct base_of<raw_attribute_vector> {
using type = base_attribute_vector;
};
And then
auto p = make_templated<raw_attribute_vector>(s);
Demo
I'd probably do something like this:
Features:
1 - time registration of objects by passing a named prototype
constant time lookup at runtime
lookup by any type which can be compared to std::string
-
#include <unordered_map>
#include <string>
struct base_attribute_vector { virtual ~base_attribute_vector() = default; };
template<class Type> struct attribute_vector : base_attribute_vector {};
// copyable singleton makes handling a breeze
struct vector_factory
{
using ptr_type = std::unique_ptr<base_attribute_vector>;
template<class T>
vector_factory add(std::string name, T)
{
get_impl()._generators.emplace(std::move(name),
[]() -> ptr_type
{
return std::make_unique< attribute_vector<T> >();
});
return *this;
}
template<class StringLike>
ptr_type create(StringLike&& s) const {
return get_impl()._generators.at(s)();
}
private:
using generator_type = std::function<ptr_type()>;
struct impl
{
std::unordered_map<std::string, generator_type, std::hash<std::string>, std::equal_to<>> _generators;
};
private:
static impl& get_impl() {
static impl _ {};
return _;
}
};
// one-time registration
static const auto factory =
vector_factory()
.add("int", int())
.add("double", double())
.add("string", std::string());
int main()
{
auto v = factory.create("int");
auto is = vector_factory().create("int");
auto strs = vector_factory().create("string");
}
Largely based on Jarod42's answer, this is what I will be using:
class base_attribute_vector {};
template<typename T>
class raw_attribute_vector : public base_attribute_vector {
public:
raw_attribute_vector() {std::cout << typeid(T).name() << std::endl; }
};
template<class base, template <typename> class impl>
base* magic(std::string type) {
if(type == "int") return new impl<int>();
else if(type == "float") return new impl<float>();
}
int main() {
auto x = magic<base_attribute_vector, raw_attribute_vector>("int");
auto y = magic<base_attribute_vector, raw_attribute_vector>("float");
}
Short answer: no, you can't instruct the compiler to evaluate a runtime condition in compile time. Not even with hana.
Long answer: there are some (mostly language independent) patterns for this.
I'm assuming that your base_attribute_vector has some virtual method, most likely pure, commonly called an interface in other languages.
Which means that depending on the complexity of your real problem, you probably want a factory or an abstract factory.
You could create a factory or abstract factory without virtual methods in C++, and you could use hana for that. But the question is: is the added complexity really worth it for that (possibly really minor) performance gain?
(also if you want to eliminate every virtual call, even from base_attribute_vector, you have to make everything using that class a template, after the entry point where the switch happens)
I mean, have you implemented this with virtual methods, and measured that the cost of the virtual calls is too significant?
Edit: another, but different solution could be using a variant type with visitors, like eggs::variant.
With variant, you can create classes with functions for each parameter type, and the apply method will switch which function to run based on it's runtime type.
Something like:
struct handler {
void operator()(TypeA const&) { ... }
void operator()(TypeB const&) { ... }
// ...
};
eggs::variant< ... > v;
eggs::variants::apply(handler{}, v);
You can even use templated operators (possibly with enable_if/sfinae), if they have common parts.

Conditionally initialize a struct field - only if it exists in that struct

Working on something like a unit testing framework for a very simple API:
extern "C" void execute_command(int cmd, void *params);
Which casts the params to the appropriate struct based on the cmd argument. I can't change that interface, nor can I modify the header which specifies the commands and the different param structures (which are all POD).
I do have access to an array of something like:
{ 0 /*cmd number*/, "PARAM_STRUCT_FOR_CMD_0", sizeof(PARAM_STRUCT_FOR_CMD_0) }
These param structs have some common properties. For example, many of them have a field like void *pObject;, though it is not always the same offset. To illustrate, suppose there are three structures:
struct {
void *pObject;
int someData;
} PARAM_STRUCT_FOR_CMD_0;
struct {
float someFloatData;
void *pObject;
} PARAM_STRUCT_FOR_CMD_1;
struct {
float someFloatData;
void *pAnotherObject;
} PARAM_STRUCT_FOR_CMD_2;
These two pObject fields represent the same thing, while pAnotherObject is unrelated.
Now, on to what I actually want: I'd like to cast a void* to some struct, based on cmd, and set its pObject field, if it exists in that struct. Ideally, I'd be able to do something like:
void *pGlobalObject;
void execcmd(int cmd)
{
static uint8_t params[MAX_SIZE_OF_PARAM_STRUCT];
memset(params, 0, MAX_SIZE_OF_PARAM_STRUCT);
INIT_STRUCT_IF_POSSIBLE(cmd, (void*)params);
execute_command(cmd, params);
}
Where INIT_STRUCT_IF_POSSIBLE could be something like:
#define INIT_STRUCT_IF_POSSIBLE(cmd, str) \
do { \
switch (cmd) \
{ \
case 0: static_cast<PARAM_STRUCT_FOR_CMD_0*>(str)->pObject = pGlobalObject; break; \
case 1: static_cast<PARAM_STRUCT_FOR_CMD_1*>(str)->pObject = pGlobalObject; break; \
case 2: /* nothing, no pObject field */ break; \
} \
} while (0)
except that isn't really scalable. I have ~1000 possible commands, and let's say 5 fields which I'd like to set (no struct has all 5), and new commands can be added, so I'd like to avoid manually changing this.
The obvious solution is an extra build step that parses all the structs, and creates their initializers. Adding this extra build step is a lot of pain though, due to how the project is structured, so I'm hoping for a pure C++ solution.
If there's a way to generate the initializers using the C preprocessor, I'm all for it. If it can somehow be done using templates, just as good. I have boost and C++11 available, if it helps.
One thing that would solve this is the designated initializers, such as STR x = {.pObject = pGlobalObject; };. Unfortunately, they cause an error when the field is not available. Any way to just ignore nonexistent fields? (Yes, I know they are C only, not C++, but I can switch to C if needed)
Welcome to the world of SFINAE
template<typename T>
typename std::enable_if<
std::is_same<decltype(T::pObject), void*>::value
>::type setPobject(T *t) {
t->pObject = pGlobalObject;
}
void setPobject(void *t) { }
template<typename T>
typename std::enable_if<
std::is_same<decltype(T::someFloatData), float>::value
>::type setSomeFloatData(T *t) {
t->someFloatData = someGlobalFloat;
}
void setSomeFloatData(void *t) { }
// ...
Just call them for all your objects with the correct types and they will figure out whether they apply or not themselfs. You can also automate the casting
template<typename D>
struct Call {
static void call(void *t) {
setPobject(static_cast<D*>(t));
setSomeFloatData(static_cast<D*>(t));
}
};
// desginated initializers here for convenience (non-C++)
void (* const table[])(void*) = {
[0] = Call<PARAM_STRUCT_FOR_CMD_0>::call,
[1] = Call<PARAM_STRUCT_FOR_CMD_1>::call
// ...
};
With some SFINAE you can detect the member and (type) dispatch assignment accordingly:
#include <iostream>
#include <type_traits>
// Member variable detection
// =========================
template<typename T, typename = void>
struct has_pObject : std::false_type { };
template<typename T>
struct has_pObject<T, decltype(std::declval<T>().pObject, void())> : std::true_type { };
// Optional member variable assignment
// ===================================
namespace Detail
{
template <typename T>
void assign_pObject(T& object, void* p, std::false_type) {}
template <typename T>
void assign_pObject(T& object, void* p, std::true_type) {
object.pObject = p;
}
}
template <typename T>
void assign_pObject(T& object, void* p) {
Detail::assign_pObject(object, p, has_pObject<T>());
}
// Test
// ====
struct {
void *pObject = nullptr;
int someData = 0;
} PARAM_STRUCT_FOR_CMD_0;
struct {
float someFloatData = 0;
void *pObject = nullptr;
} PARAM_STRUCT_FOR_CMD_1;
struct {
float someFloatData = 0;
void *pAnotherObject = nullptr;
} PARAM_STRUCT_FOR_CMD_2;
int main()
{
int object;
assign_pObject(PARAM_STRUCT_FOR_CMD_0, &object);
assign_pObject(PARAM_STRUCT_FOR_CMD_1, &object);
assign_pObject(PARAM_STRUCT_FOR_CMD_2, &object);
std::cout << PARAM_STRUCT_FOR_CMD_0.pObject << '\n';
std::cout << PARAM_STRUCT_FOR_CMD_1.pObject << '\n';
std::cout << PARAM_STRUCT_FOR_CMD_2.pAnotherObject << '\n';
return 0;
}

Dynamically define a function return type

I have a Message class that is able to pack its payload to binary and unpack it back. Like:
PayloadA p;
msg->Unpack(&p);
where PayloadA is a class.
The problem is that I have a bunch of payloads, so I need giant if or switch statement:
if (msg->PayloadType() == kPayloadTypeA)
{
PayloadA p;
msg->Unpack(&p); // void Unpack(IPayload *);
// do something with payload ...
}
else if ...
I want to write a helper function that unpacks payloads. But what would be the type of this function? Something like:
PayloadType UnpackPayload(IMessage *msg) { ... }
where PayloadType is a typedef of a proper payload class. I know it is impossible but I looking for solutions like this. Any ideas?
Thanks.
I would split one level higher to avoid the problem entirely:
#include <map>
#include <functional>
...
std::map<int, std::function<void()> _actions;
...
// In some init section
_actions[kPayloadA] = [](IMessage* msg) {
PayloadA p;
msg->Unpack(&p);
// do something with payload ...
};
// repeat for all payloads
...
// decoding function
DecodeMsg(IMessage* msg) {
_actions[id](msg);
}
To further reduce the code size, try to make Unpack a function template (possible easily only if it's not virtual, if it is you can try to add one level of indirection so that it isn't ;):
class Message {
template <class Payload>
Payload Unpack() { ... }
};
auto p = msg->Unpack<PayloadA>();
// do something with payload ...
EDIT
Now let's see how we can avoid writing the long list of _actions[kPayloadN]. This is highly non trivial.
First you need a helper to run code during the static initialization (i.e. before main):
template <class T>
class Registrable
{
struct Registrar
{
Registrar()
{
T::Init();
}
};
static Registrar R;
template <Registrar& r>
struct Force{ };
static Force<R> F; // Required to force all compilers to instantiate R
// it won't be done without this
};
template <class T>
typename Registrable<T>::Registrar Registrable<T>::R;
Now we need to define our actual registration logic:
typedef map<int, function<void()> PayloadActionsT;
inline PayloadActionsT& GetActions() // you may move this to a CPP
{
static PayloadActionsT all;
return all;
}
Then we factor in the parsing code:
template <class Payload>
struct BasePayload : Registrable<BasePayload>
{
static void Init()
{
GetActions()[Payload::Id] = [](IMessage* msg) {
auto p = msg->Unpack<Payload>();
p.Action();
}
}
};
Then we define all the payloads one by one
struct PayloadA : BasePayload<PayloadA>
{
static const int Id = /* something unique */;
void Action()
{ /* what to do with this payload */ }
}
Finally we parse the incoming messages:
void DecodeMessage(IMessage* msg)
{
static const auto& actions = GetActions();
actions[msg->GetPayloadType]();
}
How about a Factory Method that creates a payload according to the type, combined with a payload constructor for each payload type, taking a message as a parameter?
There's no avoiding the switch (or some similar construct), but at least it's straightforward and the construction code is separate from the switch.
Example:
class PayloadA : public Payload
{
public:
PayloadA(const &Message m) {...} // unpacks from m
};
class PayloadB : public Payload
{
public:
PayloadB(const &Message m) {...} // as above
};
Payload * UnpackFromMessage(const Message &m)
{
switch (m.PayloadType) :
case TypeA : return new PayloadA(m);
case TypeB : return new PayloadB(m);
... etc...
}
I seen this solved with unions. The first member of the union is the type of packet contained.
Examples here: What is a union?
An important question is how the payloads differ, and how they are the same. A system whereby you produce objects of a type determined by the payload, then interact with them via a virtual interface that is common to all types of payload, is reasonable in some cases.
Another option assuming you have a finite and fixed list of types of payload, returning a boost::variant is relatively easy. Then to process it, call apply_visitor with a functor that accepts every type in the variant.
If you only want to handle one type of payload differently, a "call and run the lambda if and only if the type matches T" function isn't that hard to write this way.
So you can get syntax like this:
struct State;
struct HandlePayload
{
typedef void return_type;
State* s;
HandlePayload(State* s_):s(s_) {}
void operator()( int const& payload ) const {
// handle int here
}
void operator()( std::shared_ptr<bob> const& payload ) const {
// handle bob ptrs here
}
template<typename T>
void operator()( T const& payload ) const {
// other types, maybe ignore them
}
}
which is cute and all, but you'll note it is quite indirect. However, you'll also note that you can write template code with a generic type T above to handle the payload, and use stuff like traits classes for some situations, or explicit specialization for others.
If you expect the payload to be one particular kind, and only want to do some special work in that case, writing a single-type handler on a boost::variant is easy.
template<typename T, typename Func>
struct Helper {
typedef bool return_type;
Func f;
Helper(Func f_):f(f_) {}
bool operator()(T const& t) {f(t); return true; }
template<typename U>
bool operator()(U const& u) { return false; }
};
template<typename T, typename Variant, typename Func>
bool ApplyFunc( Variant const& v, Func f )
{
return boost::apply_visitor( Helper<T, Func>(f), v );
}
which will call f on a variant v but only on the type T in the Variant, returning true iff the type is matched.
Using this, you can do stuff like:
boost::variant<int, double> v = 1.0;
boost::variant<int, double> v2 = int(1);
ApplyFunc<double>( v, [&](double d) { std::cout << "Double is " << d << "\n"; } );
ApplyFunc<double>( v2, [&](double d) { std::cout << "code is not run\n"; } );
ApplyFunc<int>( v2, [&](int i) { std::cout << "code is run\n"; } );
or some such variant.
One good solution is a common base class + all payloads inheriting from that class:
class PayLoadBase {
virtual char get_ch(int i) const=0;
virtual int num_chs() const=0;
};
And then the unpack would look like this:
class Unpacker {
public:
PayLoadBase &Unpack(IMessage *msg) {
switch(msg->PayLoadType()) {
case A: a = *msg; return a;
case B: b = *msg; return b;
...
}
}
private:
PayLoadA a;
PayLoadB b;
PayLoadC c;
};
You can make the function return a void *. A void pointer can be cast to any other type.