I am dealing with a network where messages(request as well as responses) are transferred as structs. For achieving this, I turned to boost-serialization, which works great! but with so many types of messages and responses, it's hard to include the serialize function in all of them, is there a shortcut to auto generate the method for all structs or at least exposing every member variable at once?
Example:
#pragma once
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/serialization.hpp>
struct Test
{
public:
int a;
int b;
template<typename archive> void serialize(archive& ar, const unsigned version) {
ar & a;
ar & b;
}
};
I believe I answered this yesterday:
struct Test {
int a,b;
template<typename Ar> void serialize(Ar& ar, unsigned) { ar & a & b; }
};
Note that if you make serialize a free function as well (ADL lookup is used):
struct Test {
int a,b;
};
template<typename Ar> void serialize(Ar& ar, Test& o, unsigned) {
ar & o.a & o.b;
}
So you can have the serialization code separately. Finally, if you have a predefined set of archives the serialize function doesn't need to be a template at all:
using OArchive = boost::archive::binary_oarchive;
using IArchive = boost::archive::binary_iarchive;
struct Test {
int a,b;
void serialize(OArchive& ar, ...) const { ar & a & b; }
void serialize(IArchive& ar, ...) { ar & a & b; }
};
Of course this creates some duplication. I got cute to ignore the version argument with variadics, but on the flip size it's more const
-correct.
If your structs are binary serializable, mark them as such:
Or explicitly treat them as blobs: make_binary_object
- in this case you don't need any serialization methods:
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/binary_object.hpp>
#include <iostream>
using boost::serialization::make_binary_object;
struct Test { int a,b; };
int main() {
boost::archive::text_oarchive oa(std::cout, boost::archive::no_header);
Test req {13,31};
oa << make_binary_object(&req, sizeof(req));
}
Prints
DQAAAB8AAAA=
Fair warning, as a C programmer you probably want to bail out and just use more preprocessor magic/code generators here
Say you have more messages (which can nest):
namespace Messages {
struct FooMsg { int a, b; };
struct BarMsg { std::string c; double d; };
struct QuxMsg { FooMsg e; BarMsg f; };
}
You can adapt these as Fusion Sequences:
BOOST_FUSION_ADAPT_STRUCT(Messages::FooMsg, a, b)
BOOST_FUSION_ADAPT_STRUCT(Messages::BarMsg, c, d)
BOOST_FUSION_ADAPT_STRUCT(Messages::QuxMsg, e, f)
The good thing is now you can write generic code across these sequences, so let's introduce our very own serialization wrapper:
namespace Messages {
template <typename T>
struct MsgSerializationWrapper {
T& ref;
};
template <typename T>
static inline MsgSerializationWrapper<T> wrap(T& msg) { return {msg}; }
Now you can implement serialization for any wrapped message:
template <typename Ar, typename Msg>
void serialize(Ar& ar, MsgSerializationWrapper<Msg> wrapped, unsigned) {
boost::fusion::for_each(wrapped.ref, [&ar](auto& field) { ar & wrap(field); });
}
Of course, we need some sfinae to detect when the wrapped type is not a fusion sequence and just serialize that the normal way.
#include <boost/archive/text_oarchive.hpp>
#include <boost/fusion/adapted.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <iostream>
namespace Messages {
struct FooMsg { int a, b; };
struct BarMsg { std::string c; double d; };
struct QuxMsg { FooMsg e; BarMsg f; };
}
BOOST_FUSION_ADAPT_STRUCT(Messages::FooMsg, a, b)
BOOST_FUSION_ADAPT_STRUCT(Messages::BarMsg, c, d)
BOOST_FUSION_ADAPT_STRUCT(Messages::QuxMsg, e, f)
namespace Messages {
template <typename T>
struct MsgSerializationWrapper {
T& ref;
};
template <typename T>
static inline MsgSerializationWrapper<T> wrap(T& msg) { return {msg}; }
template <typename Ar, typename Msg>
std::enable_if_t<boost::fusion::traits::is_sequence<Msg>::value>
serialize(Ar& ar, MsgSerializationWrapper<Msg> wrapped, unsigned) {
boost::fusion::for_each(wrapped.ref, [&ar](auto& field) { ar & wrap(field); });
}
template <typename Ar, typename Primitive>
std::enable_if_t<not boost::fusion::traits::is_sequence<Primitive>::value>
serialize(Ar& ar, MsgSerializationWrapper<Primitive> wrapped, unsigned) {
ar & wrapped.ref;
}
}
int main() {
boost::archive::text_oarchive oa(std::cout);
Messages::QuxMsg req {
Messages::FooMsg { 42, 99 },
Messages::BarMsg { "hello world\n", 3.14e100 },
};
oa << wrap(req);
}
Prints
22 serialization::archive 17 0 0 0 0 0 0 42 99 0 0 0 0 12 hello world
0 0 3.13999999999999984e+100