Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Runtime value to type mapping

Tags:

c++

networking

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.

like image 819
Robin Avatar asked Sep 29 '12 14:09

Robin


3 Answers

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.

like image 154
Matthieu M. Avatar answered Oct 06 '22 15:10

Matthieu M.


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.

like image 31
wolfgang Avatar answered Oct 06 '22 15:10

wolfgang


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);
like image 2
PiotrNycz Avatar answered Oct 06 '22 16:10

PiotrNycz