Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return a member variable if it exist

Tags:

c++

boost

sfinae

Somehow I feel there are several answer to the similar question, however I couldn't find a final solution to my problem.So, my apologies in advance: I have many message structures that are either incoming:

struct X_1 //Y_2, Z_x, _...
{
 IncomingHeader incoming_header;
 //.......
};

or Outgoing:

struct A_1 //B_2, C_x, _...
{
 OutgoingHeader outgoing_header;
 //.......
};

The message headers are of only two types:

struct IncomingHeader
{
  A a;
  B b;
};


struct OutgoingHeader
{
  A a;
  B b;
  char c[SIZE};
};

//If it helps, eventually I am only interested in a and b in header structs.

At some point during decoding, I need a get_header() function which would return the header member(incoming_header or outgoing_header). Is there a way to solve this problem? (I am using boost 1.46 and not C++11)

like image 967
rahman Avatar asked Feb 15 '26 00:02

rahman


1 Answers

Well, Walter addressed the idea, by introducing a common base type. But generally there are 2 approaches how one can handle the encoded/marshalled data.

  • Direct mapping of network bytes to data structures, e.g. C/C++ POD types
  • Using system independent data representation format, e.g. Google Protobuf, XDR, ASN.1 and many more (even non binary ones like XML, JSON, YAML...)

Case 1: POD like handling

C/C++ POD

Actually, the only part I don not agree with Walter's idea is to introduce the virtual base class. Especially, because the type no longer is a POD and one can't map it 1:1 to the network bytes and needs to copy data.

Usually types like A, B from your example are designed as PODs. And that allows very efficient marshalling/unmarshalling of them without copying.

Let's say you have smth. like:

struct incoming_header
{
  std::int32_t a;
  std::int64_t b;
};

struct outgoing_header
{
  std::int32_t a;
  std::int64_t b;
  char c[SIZE};
};

Here we use C++ standard's guaranteed length integers to be sure that we deal with exact length of fields. Unfortunately, standard defines that they are optional and thus might not be available on your target platform (which actually seldom for fully fledged HW and likely to be the case on some embedded HW).

Sending PODs

Now because these types are POD we can simply send them by simply pushing their bytes through the network.

So the following pseudo code is fully OK:

outgoing_header oh{...};
send(&oh, sizeof(oh));

Receiving PODs

Usually you know how (from your protocol how many bytes you need), given they are all copied into contiguous buffer you can obtain a proper view to that buffer. Let's say we do not deal with big/little endian issues at that point. Then what the networking code usually receives bytes for you and states how many these are.

So at that point let's rely that we can only receive outgoing_header right now and our buffer is big enough to contain the entire message length.

Then the code usually looks similar to:

constexpr static size_t max_size = ...;
char buf[max_size]{};

size_t got_bytes = receive(&buf, max_size);

// now we need to interpret these bytes as outgoing_header
outgoing_header* pheader = reinterpret_cast<outgoing_header*>(&buf[0]);

// now access the header items
pheader->a;
pheader->b;

There are no copies involved, just a pointer cast.

Solving your problem

Usually any binary protocol has one common header sender and receiver can rely on. There is encoded, which message is being carried, how long it is, may be the protocol version etc.

What you need to do is to introduce a common header, in your case it should be carrying field a and b.

struct base_header
{
  std::int32_t a;
  std::int64_t b;
};

// Note! Using derivation will render the type as non-POD, thus aggregation
struct incoming_header
{
  base_header base;
};

struct outgoing_header
{
  base_header base;
  char c[SIZE};
};

Now incoming_header and outgoing_header are both PODs. What you need to do here is to cast your buffer to the pointer to the base_header and grab a and b of interest:

base_header* pbase_header = reinterpret_cast<base_header*>(&buf[0]);
do_smth(pbase_header->a, pbase_header->b);

Case 2: System independent data representation format

An alternative to that approach would be to use boost::variant class or if you switch to C++17 std::variant. In case that you can't have PODs and have some sort of custom serialization format with custom marshalling/unmarshalling lib e.g. Google Protobuf or alike...

Using variant you can just define your protocol, i.e. messages/headers which might arrive:

typedef boost::variant<boost::none, IncomingHeader, OutgoingHeader> message_header;

message_header get_header(char* bytes, size_t size)
{
  // dispatch bytes and put the message to variant:
  // let's say we get OutgoingHeader
  OutgoingHeader h{/* init from bytes here */};
  return h; // variant has implicit ctor to accept OutgoingHeader object
}

Now you can use a hand crafted visitor type to get the desired value:

struct my_header_visitor
{
  typedef void result_type;

  explicit my_header_visitor(some_context& ctx)
    : ctx_{ctx}
  {}

  template<class T>
  result_type operator()(T const&)
  {
    // throw whatever error, due to unexpected dispatched type
  }

  result_type operator()(OutgoingHeader const& h)
  {
     // handle OutgoingHeader
     ctx_.do_smth_with_outgoing_header(h);
  }

  result_type operator()(IncomingHeader const& h)
  {
    // handle IncomingHeader
    ctx_.do_smth_with_incoming_header(h);
  }

private:
  some_context& ctx_;
};

my_header_visitor v{/* pass context here */};
message_header h {/* some init code here */};
boost::apply_visitor(v, h);

P.S. if you are interested to understand why variant is needed or how the dispatching works, you can read Andrei Alexandrescu's article series on discriminated unions in Dr. Dobbs:

  • Discriminated Unions (I)
  • Discriminated Unions (II)
  • Generic: Discriminated Unions (III)
like image 145
ovanes Avatar answered Feb 16 '26 14:02

ovanes



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!