1

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

1 Answers1

1

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.

Other thoughts

If your structs are binary serializable, mark them as such:

  • Boost serialization bitwise serializability
  • Or explicitly treat them as blobs: make_binary_object - in this case you don't need any serialization methods:

    Live On Coliru

    #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=
    

META-PROGRAMMING TO THE RESCUE

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.

Full Demo

Live On Coliru

#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
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Added hints on (non portable) bitwise/binary object serialization – sehe Jun 05 '20 at 20:44
  • 1
    Answered the broader question with some meta-programming as well: META-PROGRAMMING TO THE RESCUE ([demo](http://coliru.stacked-crooked.com/a/470f7390a67aaded)) – sehe Jun 05 '20 at 21:05
  • Thanks for such a informative answer, not only it answers my question but also provides alternatives. – DEEPANSH NAGARIA Jun 08 '20 at 05:20