maverik
maverik

Reputation: 5586

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.

Upvotes: 3

Views: 1633

Answers (6)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275395

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.

Upvotes: 1

J.N.
J.N.

Reputation: 8421

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]();
}

Upvotes: 2

Roddy
Roddy

Reputation: 68033

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...
}

Upvotes: 2

tp1
tp1

Reputation: 1207

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;
};

Upvotes: 0

Steve Wellens
Steve Wellens

Reputation: 20620

I seen this solved with unions. The first member of the union is the type of packet contained.

Examples here: What is a union?

Upvotes: 1

Philipp
Philipp

Reputation: 69663

You can make the function return a void *. A void pointer can be cast to any other type.

Upvotes: -1

Related Questions