Choose template based on run-time string in C++ - 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.

Related

Template a member variable

Consider the following two classes:
class LunchBox
{
public:
std::vector<Apple> m_apples;
};
and
class ClassRoom
{
public:
std::vector<Student> m_students;
};
The classes are alike in that they both contain a member variable vector of objects; however, they are unalike in that the vector's objects are different and the member variables have different names.
I would like to write a template that takes either LunchBox or ClassRoom as a template argument (or some other parameter) and an existing object of the same type (similar to a std::shared_ptr). The template would return an object that adds a getNthElement(int i); member function to improve accessing the methods. Usage would be like:
// lunchBox is a previously initialized LunchBox
// object with apples already pushed into m_apples
auto lunchBoxWithAccessor = MyTemplate<LunchBox>(lunchBox);
auto apple3 = lunchBoxWithAccessor.getNthElement(3);
I would like to do this without writing template specializations for each class (which likely would require specifying the member variable to operate on in some way). Preferably, I do not want to modify the LunchBox or ClassRoom classes. Is writing such a template possible?
You can minimize the amount of code that has to be written for each class -- it doesn't have to be a template specialization and it doesn't have to be an entire class.
class LunchBox
{
public:
std::vector<Apple> m_apples;
};
class ClassRoom
{
public:
std::vector<Student> m_students;
};
// you need one function per type, to provide the member name
auto& get_associated_vector( Student& s ) { return s.m_apples; }
auto& get_associated_vector( ClassRoom& r ) { return r.m_students; }
// and then the decorator is generic
template<typename T>
class accessor_decorator
{
T& peer;
public:
auto& getNthElement( int i ) { return get_associated_vector(peer).at(i); }
auto& takeRandomElement( int i ) { ... }
// many more ways to manipulate the associated vector
auto operator->() { return &peer; }
};
LunchBox lunchBox{};
accessor_decorator<LunchBox> lunchBoxWithAccessor{lunchBox};
auto apple3 = lunchBoxWithAccessor.getNthElement(3);
The simple helper function overload should ideally be in the same namespace as the type, to make argument-dependent lookup work (aka Koenig lookup).
It's also possible to specify the member at the point of construction, if you prefer to do that:
template<typename T, typename TMemberCollection>
struct accessor_decorator
{
// public to make aggregate initialization work
// can be private if constructor is written
T& peer;
TMemberCollection const member;
public:
auto& getNthElement( int i ) { return (peer.*member).at(i); }
auto& takeRandomElement( int i ) { ... }
// many more ways to manipulate the associated vector
auto operator->() { return &peer; }
};
template<typename T, typename TMemberCollection>
auto make_accessor_decorator(T& object, TMemberCollection T::*member)
-> accessor_decorator<T, decltype(member)>
{
return { object, member };
}
LunchBox lunchBox{};
auto lunchBoxWithAccessor = make_accessor_decorator(lunchBox, &LunchBox::m_apples);
auto apple3 = lunchBoxWithAccessor.getNthElement(3);
A simple way to do this is define a trait struct that has specializations with just the information that makes each case different. Then you have a template class that uses this traits type:
// Declare traits type. There is no definition though. Only specializations.
template <typename>
struct AccessorTraits;
// Specialize traits type for LunchBox.
template <>
struct AccessorTraits<LunchBox>
{
typedef Apple &reference_type;
static reference_type getNthElement(LunchBox &box, std::size_t i)
{
return box.m_apples[i];
}
};
// Specialize traits type for ClassRoom.
template <>
struct AccessorTraits<ClassRoom>
{
typedef Student &reference_type;
static reference_type getNthElement(ClassRoom &box, std::size_t i)
{
return box.m_students[i];
}
};
// Template accessor; uses traits for types and implementation.
template <typename T>
class Accessor
{
public:
Accessor(T &pv) : v(pv) { }
typename AccessorTraits<T>::reference_type getNthElement(std::size_t i) const
{
return AccessorTraits<T>::getNthElement(v, i);
}
// Consider instead:
typename AccessorTraits<T>::reference_type operator[](std::size_t i) const
{
return AccessorTraits<T>::getNthElement(v, i);
}
private:
T &v;
};
A few notes:
In this case, the implementation would technically be shorter without a traits type; with only specializations of Accessor for each type. However, the traits pattern is a good thing to learn as you now have a way to statically reflect on LunchBox and ClassRoom in other contexts. Decoupling these pieces can be useful.
It would be more idiomatic C++ to use operator[] instead of getNthElement for Accessor. Then you can directly index the accessor objects.
AccessorTraits really isn't a good name for the traits type, but I'm having trouble coming up with anything better. It's not the traits of the accessors, but the traits of the other two relevant classes -- but what concept even relates those two classes? (Perhaps SchoolRelatedContainerTraits? Seems a bit wordy...)
You said:
I would like to do this without writing template specializations for each class
I am not sure why that is a constraint. What is not clear is what else are you not allowed to use.
If you are allowed to use couple of function overloads, you can get what you want.
std::vector<Apple> const& getObjects(LunchBox const& l)
{
return l.m_apples;
}
std::vector<Student> const& getObjects(ClassRoom const& c)
{
return c.m_students;
}
You can write generic code that works with both LaunchBox and ClassRoom without writing any other specializations. However, writing function overloads is a form of specialization.
Another option will be to update LaunchBox and ClassRoom with
class LunchBox
{
public:
std::vector<Apple> m_apples;
using ContainedType = Apple;
};
class ClassRoom
{
public:
std::vector<Student> m_students;
using ContainedType = Apple;
};
and then, take advantage of the fact that
LaunchBox b;
std::vector<Apple>* ptr = reinterpret_cast<std::vector<Apple>*>(&b);
is a legal construct. Then, the following class will work fine.
template <typename Container>
struct GetElementFunctor
{
using ContainedType = typename Container::ContainedType;
GetElementFunctor(Container const& c) : c_(c) {}
ContainedType const& getNthElement(std::size_t n) const
{
return reinterpret_cast<std::vector<ContainedType> const*>(&c_)->operator[](n);
}
Container const& c_;
};
and you can use it as:
LunchBox b;
b.m_apples.push_back({});
auto f = GetElementFunctor<LunchBox>(b);
auto item = f.getNthElement(0);
I did a test case sample using a few basic classes:
class Apple {
public:
std::string color_;
};
class Student {
public:
std::string name_;
};
class LunchBox {
public:
std::vector<Apple> container_;
};
class ClassRoom {
public:
std::vector<Student> container_;
};
However for the template function that I wrote I did however have to change the name of the containers in each class to match for this to work as this is my template function:
template<class T>
auto accessor(T obj, unsigned idx) {
return obj.container_[idx];
}
And this is what my main looks like:
int main() {
LunchBox lunchBox;
Apple green, red, yellow;
green.color_ = std::string( "Green" );
red.color_ = std::string( "Red" );
yellow.color_ = std::string( "Yellow" );
lunchBox.container_.push_back(green);
lunchBox.container_.push_back(red);
lunchBox.container_.push_back(yellow);
ClassRoom classRoom;
Student s1, s2, s3;
s1.name_ = std::string("John");
s2.name_ = std::string("Sara");
s3.name_ = std::string("Mike");
classRoom.container_.push_back(s1);
classRoom.container_.push_back(s2);
classRoom.container_.push_back(s3);
for (unsigned u = 0; u < 3; u++) {
auto somethingUsefull = accessor(lunchBox, u);
std::cout << somethingUsefull.color_ << std::endl;
auto somethingElseUsefull = accessor(classRoom, u);
std::cout << somethingElseUsefull.name_ << std::endl;
}
return 0;
}
I'm not sure if there is a work around to have a different variable name from each different class this function can use; but if there is I haven't figured it out as of yet. I can continue to work on this to see if I can improve it; but this is what I have come up with so far.

C++ Template : one list by class, how to factorize the code?

Suppose I have this class :
class Component1;
class Component2;
// many different Components
class Component42;
class MyClass
{
public:
MyClass(void) {};
std::list<Component1> component1List;
std::list<Component2> component2List;
// one list by component
std::list<Component42> component42List;
};
I would like to create a function with the following signature:
template<class T> void addElement(T component);
It should do the following:
if component is of type Component1, add it to Component1List
if component is of type Component2, add it to Component2List, etc.
Is it possible? What's a good way to do this?
I can obtain the same behaviour with a function like :
template<class T> void addElement(int componentType, T component);
but I'd rather not have to specify the componentType like this : it's useless information and it open the door to possible errors (if componentType doesn't represent the type of component).
std::tuple to the rescue.
changelog:
use std::decay_t
added the variadic argument form
add_component() now returns a reference to this to allow call-chaining.
#include <iostream>
#include <list>
#include <utility>
#include <type_traits>
#include <tuple>
class Component1 {};
class Component2 {};
struct Component3 {
Component3() {}
};
// many different Components
template<class...ComponentTypes>
class MyClassImpl
{
template<class Component> using list_of = std::list<Component>;
public:
using all_lists_type =
std::tuple<
list_of<ComponentTypes> ...
>;
// add a single component
template<class Component>
MyClassImpl& add_component(Component&& c)
{
list_for<Component>().push_back(std::forward<Component>(c));
return *this;
}
// add any number of components
template<class...Components>
MyClassImpl& add_components(Components&&... c)
{
using expand = int[];
void(expand { 0, (void(add_component(std::forward<Components>(c))), 0)... });
return *this;
}
template<class Component>
auto& list_for()
{
using component_type = std::decay_t<Component>;
return std::get<list_of<component_type>>(_lists);
}
template<class Component>
const auto& list_for() const
{
using component_type = std::decay_t<Component>;
return std::get<list_of<component_type>>(_lists);
}
private:
all_lists_type _lists;
};
using MyClass = MyClassImpl<Component1, Component2, Component3>;
int main()
{
MyClass c;
c.add_component(Component1());
c.add_component(Component2());
const Component3 c3;
c.add_component(c3);
c.add_components(Component1(),
Component2(),
Component3()).add_components(Component3()).add_components(Component1(),
Component2());
std::cout << c.list_for<Component1>().size() << std::endl;
return 0;
}
The most straightforward variant is to simply not use templates but to overload the addElement() function:
void addElement(Component1 element)
{
this->element1List.push_back(element);
}
void addElement(Component2 element)
{
this->element2List.push_back(element);
}
// ... etc
However, this might get tedious if you have many of these (and you don't just have addElement(), I guess). Using a macro to generate the code for each type could still do the job with reasonable effort.
If you really want to use templates, you could use a template function and specialize the template function for each type. Still, this doesn't reduce the amount of code repetition when compared with the above approach. Also, you could still reduce it using macros to generate the code.
However, there's hope for doing this in a generic way. Firstly, let's create a type that holds the list:
template<typename T>
struct ComponentContainer
{
list<T> componentList;
};
Now, the derived class just inherits from this class and uses C++ type system to locate the correct container baseclass:
class MyClass:
ComponentContainer<Component1>,
ComponentContainer<Component2>,
ComponentContainer<Component3>
{
public:
template<typename T>
void addElement(T value)
{
ComponentContainer<T>& container = *this;
container.componentList.push_back(value);
}
}
Notes here:
This uses private inheritance, which is very similar to the containment you originally used.
Even though ComponentContainer is a baseclass, it doesn't have any virtual functions and not even a virtual destructor. Yes, this is dangerous and should be documented clearly. I wouldn't add a virtual destructor though, because of the overhead it has and because it shouldn't be needed.
You could drop the intermediate container altogether and derive from list<T>, too. I didn't because it will make all of list's memberfunctions available in class MyClass (even if not publicly), which might be confusing.
You can't put the addElement() function into the base class template to avoid the template in the derived class. The simple reason is that the different baseclasses are scanned in order for a addElement() function and only then overload resolution is performed. The compiler will only find the addElement() in the first baseclass therefore.
This is a plain C++98 solution, for C++11 I'd look at the type-based tuple lookup solutions suggested by Jens and Richard.
If there are not too many classes you could go with overloading. A template-based solution could be done with type-based lookup for tuples:
class MyClass {
public:
template<typename T> void addElement(T&& x) {
auto& l = std::get<std::list<T>>(lists);
l.insert( std::forward<T>(x) );
}
private:
std::tuple< std::list<Component1>, std::list<Component2> > lists;
};
If you don't know in advance the types you will need storing when instantiating the multi-container an option is to hide the types and using type_index to keep a map of lists:
struct Container {
struct Entry {
void *list;
std::function<void *(void*)> copier;
std::function<void(void *)> deleter;
};
std::map<std::type_index, Entry> entries;
template<typename T>
std::list<T>& list() {
Entry& e = entries[std::type_index(typeid(T))];
if (!e.list) {
e.list = new std::list<T>;
e.deleter = [](void *list){ delete ((std::list<T> *)list); };
e.copier = [](void *list){ return new std::list<T>(*((std::list<T> *)list)); };
}
return *((std::list<T> *)e.list);
}
~Container() {
for (auto& i : entries) i.second.deleter(i.second.list);
}
Container(const Container& other) {
// Not exception safe... se note
for (auto& i : other.entries) {
entries[i.first] = { i.second.copier(i.second.list),
i.second.copier,
i.second.deleter };
}
};
void swap(Container& other) { std::swap(entries, other.entries); }
Container& operator=(const Container& other) {
Container(other).swap(*this);
return *this;
};
Container() { }
};
that can be used as:
Container c;
c.list<int>().push_back(10);
c.list<int>().push_back(20);
c.list<double>().push_back(3.14);
NOTE: the copy constructor as written now is not exception safe because in case a copier throws (because of an out of memory or because a copy constructor of an element inside a list throws) the already allocated lists will not be deallocated.
void addElement(Component1 component) {
componentList1.insert(component);
}
void addElement(Component2 component) {
componentList2.insert(component);
}

C++ auto deduction of return type

I want to write a function that return different types based on different input as below.
enum MyType
{
A,
B
};
template<MyType T> struct MyStruct
{
};
static auto createMyStruct(MyType t)
{
if(t==A)
return MyStruct<A>();
else
return MyStruct<B>();
}
It didn't work out because there are two return types for one auto. Is there any other way to do this?
There is absolutely no way of having a (single) function that returns different types based on a runtime decision. The return type has to be known at compile time. However, you can use a template function, like this (thanks to #dyp for making me simplify the code):
#include <iostream>
#include <typeinfo>
enum MyType
{
A,
B
};
template<MyType>
struct MyStruct {};
template<MyType type>
MyStruct<type> createMyStruct()
{
return {};
}
int main()
{
auto structA = createMyStruct<A>();
auto structB = createMyStruct<B>();
std::cout << typeid(structA).name() << std::endl;
std::cout << typeid(structB).name() << std::endl;
}
I am assuming you want to write code like this:
void foo (MyType t) {
auto x = createMyStruct(t);
//... do something with x
}
You are attempting to derive the right type for x at runtime. However, the return type of a function must be known at compile time, and the type resolution for auto is also determined at compile time.
You could instead restructure your code to be like this:
template<MyType T> struct MyStruct
{
//...
static void foo () {
MyStruct x;
//... do something with x
}
};
The idea is to write a single foo() function whose only difference is the type of thing it is manipulating. This function is encapsulated within the type itself. You can now make a runtime decision if you have a mapping between MyType and MyStruct<MyType>::foo.
typedef std::map<MyType, void(*)()> MyMap;
template <MyType...> struct PopulateMyMap;
template <MyType T> struct PopulateMyMap<T> {
void operator () (MyMap &m) {
m[T] = MyStruct<T>::foo;
}
};
template <MyType T, MyType... Rest> struct PopulateMyMap<T, Rest...> {
void operator () (MyMap &m) {
m[T] = MyStruct<T>::foo;
PopulateMyMap<Rest...>()(m);
}
};
template<MyType... Types> void populateMyMap (MyMap &m) {
PopulateMyMap<Types...>()(m);
}
//...
populateMyMap<A, B>(myMapInstance);
Then, to make a runtime decision:
void foo (MyType t) {
myMapInstance.at(t)();
}
I think you should learn abstract factory design pattern.
For use objects of type MyStruct<A> or MyStruct<B> you need common interface.
Common interface provided in abstract base class.
struct MyStruct
{
virtual ~MyStruct() {}
virtual void StructMethod() = 0;
};
struct MyStructA: public MyStruct
{
void StructMethod() override {}
};
struct MyStructB: public MyStruct
{
void StructMethod() override {}
};
std::unique_ptr<MyStruct> createMyStruct(MyType t)
{
if (t==A)
return std::make_unique<MyStructA>();
else
return std::make_unique<MyStructB>();
}

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.

Runtime value to type mapping

I've got a list of types which can be send over the network, take this example:
enum types {
E_T1,
E_T2,
E_T3,
E_T4
};
Now I have a list of classes which correspond to each of the types, let's say each is declared as class E_T1 {...}, class E_T2 {...}, etc.
They are not derived from a common base class and it's not possible to do so. Each of the classes has a verification method I need to invoke with the data send over the network. The client sends the data D and a id correspointing to the message type. I need to get hold of the object corresponding to the type. I can use C++0x features if needed.
What I've tried so far is using specialized templates for the types, holding a typedef for the object related to it. This was obviously a stupid idea as templates parameters need to be compile time constant so doing something along getType<data.id()>::type is not possible.
Then I tried using Boost.Variant to get a common returnable type like this (used mpl vector to iterate over the registered types at runntime for debbuging):
template <typename C>
struct getType() {
typedef C type;
}
typedef boost::mpl::vector<
getType<E_T1>,
getType<E_T2>,
getType<E_TX>...
> _types;
typedef boost::make_variant_over<_types>::type _type;
//use a map to store each type <-> id
boost::unorderd_map<types, _type> m;
m[E_T1] = getType<E_T1>();
m[data.id()]::type x; //<- access type, can now call x.validate(data)
The problem with this is that it's limited to 20 entries per variant per default. This can be overwritten but from what I understood the overhead per type should be considered and we are talking about a few thousand types here.
Also tried boost.any but it doesn't hold any type information so that's out of the question again. Has anyone any good ideas how this can be solved elegantly?
Looking for something where I don't have to write a 1k switch statement anytime I handle a type.
All types are nown at compile type, same goes for their corresponding IDs.
Id -> Type resolving needs to happen at runtime though.
Thanks in advance,
Robin.
External Polymorphism (*)
It's a widely known idiom, however it's widely used: I first encountered it in the shared_ptr implementation and it's been quite useful in my toolbox.
The idea is to actually create a base class for all those types. But not having them derive from it directly.
class Holder {
public:
virtual ~Holder() {}
virtual void verify(unsigned char const* bytes, size_t size) const = 0;
}; // class Holder
template <typename T>
class HolderT: public Holder {
public:
HolderT(): _t() {}
virtual void verify(unsigned char const* bytes, size_t size) const {
_t.verify();
}
private:
T _t;
}; // class HolderT
template <typename T>
std::unique_ptr<Holder> make_holder() {
return std::unique_ptr<Holder>(new HolderT<T>());
}
So, it's the classic strategy of adding a new level of indirection.
Now, you obviously do need a switch to move from value to class. Or perhaps... a map ?
using maker = std::unique_ptr<Holder> (&)();
using maker_map = std::unordered_map<types, maker>;
std::unique_ptr<Holder> select(types const E) {
static maker_map mm;
if (mm.empty()) {
mm.insert(std::make_pair(E_T1, make_holder<EC_T1>));
// ...
}
maker_map::const_iterator it = mm.find(E);
if (it == mm.end()) { return std::unique_ptr<Holder>(); }
return (*it->second)();
}
And now you can handle them polymorphically:
void verify(types const E, unsigned char const* bytes, size_t size) {
std::unique_ptr<Holder> holder = select(E);
if (not holder) { std::cerr << "Unknown type " << (int)E << "\n"; return; }
holder->verify(bytes, size);
}
Of course, you're welcome to make the strategy vary according to your needs. For example moving the map out of select so that you can register your types dynamically (like for plugins).
(*) At least that's the name I have for it, I would quite happy to find out it's already been named.
I'll assume you have a generic way of handling a message, such as for example an overloaded function:
void handle_message(const E_T1& msg);
void handle_message(const E_T2& msg);
//...
Now, you do not really need to get the object's type. All you need is a way to handle a message of that type, given the undecoded message.
So, I recommend you populate a map of factory functions:
std::unordered_map<types, std::function<void (unsigned char const* bytes, size_t size)> handlers;
handlers[E_E1] = [](unsigned char const* bytes, size_t size) { handle_message(E_T1(bytes, size)); };
// ...
Then, once you've decoded the type, you can use handlers[type](bytes, size) to decode and handle a message.
Try variadic templates and your already defined getType class:
enum types { T1_ID, T2_ID, .... };
class T1; class T2; class T3; ....
template <types t> struct getType;
template <> struct getType<T1_ID> { typedef T1 type; };
template <> struct getType<T2_ID> { typedef T2 type; };
...
And the operation verify:
template <types...>
struct type_operation;
template <types t1, types... rest>
struct type_operation<t1, rest...>
{
void verify(types t)
{
if (t == t1)
{
typename getType<t1>::type a;
a.verify(); // read from network and verify the rest of data....
}
else type_operation<rest...>::verify(t, data);
}
};
template <>
struct type_operation<>
{
void verify(types t)
{
ostringstream log; log << "not suppoted: " << t;
throw std::runtime_error(log.str()); //
}
};
Usage:
typedef type_operation<T1_ID, T2_ID, T3_ID, ,,.., TN_ID> type_mapping;
types id;
readFromNetwork(id);
type_mapping::verify(id);