msgpack / msgpack-c

MessagePack implementation for C and C++ / msgpack.org[C/C++]
Other
3.03k stars 883 forks source link

Non-intrusive adapter macros #963

Open ysc3839 opened 3 years ago

ysc3839 commented 3 years ago

Currently there is only intrusive adapter macros (MSGPACK_DEFINE_ARRAY and MSGPACK_DEFINE_MAP), but no non-intrusive. A possible (incomplete) implementation is:

#define MY_DEFINE_MAP_EACH_PROC(r, data, elem) \
    MSGPACK_PP_IF( \
        MSGPACK_PP_IS_BEGIN_PARENS(elem), \
        elem, \
        (MSGPACK_PP_STRINGIZE(elem))(data.elem) \
    )

#define MY_DEFINE_MAP_IMPL(var_name, ...) \
    MSGPACK_PP_SEQ_TO_TUPLE( \
        MSGPACK_PP_SEQ_FOR_EACH( \
            MY_DEFINE_MAP_EACH_PROC, \
            var_name, \
            MSGPACK_PP_VARIADIC_TO_SEQ(__VA_ARGS__) \
        ) \
    )

#define MSGPACK_ADD_CLASS(class_name, ...) \
    namespace msgpack { \
    /** @cond */ \
    MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) { \
    /** @endcond */ \
    namespace adaptor { \
        template<> \
        struct convert<class_name> { \
            msgpack::object const& operator()(msgpack::object const& msgpack_o, class_name& msgpack_v) const { \
                msgpack::type::make_define_map \
                    MY_DEFINE_MAP_IMPL(msgpack_v, __VA_ARGS__) \
                    .msgpack_unpack(msgpack_o); \
                return msgpack_o; \
            } \
        }; \
        template <> \
        struct pack<class_name> { \
            template <typename Stream> \
            msgpack::packer<Stream>& operator()(msgpack::packer<Stream>& msgpack_pk, const class_name& msgpack_v) const { \
                msgpack::type::make_define_map \
                    MY_DEFINE_MAP_IMPL(msgpack_v, __VA_ARGS__) \
                    .msgpack_pack(msgpack_pk); \
                return msgpack_pk; \
            } \
        }; \
    } \
    /** @cond */ \
    } \
    /** @endcond */ \
    }

MSGPACK_ADD_CLASS(your_class, a, b, c) expands to:

namespace msgpack {
    inline namespace v3 {
        namespace adaptor {
            template<> struct convert<your_class> {
                msgpack::object const& operator()(msgpack::object const& msgpack_o, your_class& msgpack_v) const {
                    msgpack::type::make_define_map ("a", msgpack_v.a, "b", msgpack_v.b, "c", msgpack_v.c ) .msgpack_unpack(msgpack_o); return msgpack_o;
                }
            };
            template <> struct pack<your_class> {
                template <typename Stream> msgpack::packer<Stream>& operator()(msgpack::packer<Stream>& msgpack_pk, const your_class& msgpack_v) const {
                    msgpack::type::make_define_map ("a", msgpack_v.a, "b", msgpack_v.b, "c", msgpack_v.c ) .msgpack_pack(msgpack_pk); return msgpack_pk;
                }
            };
        }
    }
}

MSGPACK_NVP is not working with this implementation.

redboltz commented 3 years ago

Interesting idea. But non-intrusive use case is various. Users might want to have any combination of as convert pack object object_with_zone. Some of them cannot be implemented. It depends on user type. At least they should be separated. So far, intrusive macro is for causal users and non-intrusive one is for advanced users. As you mentioned MSGPACK_NVP should work well with the macros.

So far, I have no plan to providing non-intrusive macros. But pull request that solves the all issues that I mentioned, we are open to review and merge it.

hoditohod commented 2 years ago

In my projects I use both MsgPack to serizalize/deserialize objects with MSGPACK_DEFINE_MAP and Cereal to/from JSON. Cereal supports non-intrusive serialization functions and I found it to be very practical:

With MsgPack if I want a class to be serializable and add MSGPACK_DEFINE_MAP that means that I have to include MsgPack headers everywhere where the class is used, and MsgPack is not that lightweight.

redboltz commented 2 years ago

msgpack-c C++ supports non-intrusive adaptor. See https://github.com/msgpack/msgpack-c/wiki/v2_0_cpp_adaptor#non-intrusive-approach

hoditohod commented 2 years ago

Ok, indeed it is possible to write serialization functionality in a non-intrusive way, but as I see the whole purpose of MSGPACK_DEFINE_MAP is to generate this (non-trivial) boilerplate for me. From my point of view there should be a non-member MSGPACK_DEFINE_MAP to write the non-intrusive boilerplate as well. Of course I understand the technical difficulties associated with it.

redboltz commented 2 years ago

When you write a pull request that contains the following functionalities and tests then I would review it.

These are non intrusive adaptor macros. The macro name is just idea. Separated macros are better because some of them are impossible to implement due to the target class restriction. Both ARRAY and MAP adaptor should be supported. User can choose ARRAY or MAP version (maybe exclusively) by the target class. User can also choose with operation is supported.

MSGPACK_ADAPTOR_ARRAY_CONVERT(...)
MSGPACK_ADAPTOR_ARRAY_AS(...)
MSGPACK_ADAPTOR_ARRAY_PACK(...)
MSGPACK_ADAPTOR_ARRAY_OBJECT(...)
MSGPACK_ADAPTOR_ARRAY_OBJECT_WITH_ZONE(...)

MSGPACK_ADAPTOR_MAP_CONVERT(...)
MSGPACK_ADAPTOR_MAP_AS(...)
MSGPACK_ADAPTOR_MAP_PACK(...)
MSGPACK_ADAPTOR_MAP_OBJECT(...)
MSGPACK_ADAPTOR_MAP_OBJECT_WITH_ZONE(...)
Crucio32000 commented 1 month ago

I would like this to be implemented. We could start by tackling simple scenarios like a structure and packing it as an array. Interesting, the msgpack::type::make_define_array would be handy, although some macro magic is needed to create the necessary tools to generate a non-intrusive macro.

For instance: `

define MSGPACK_DEFINE_NON_INTRUSIVE(_structType, ...)

`

what do you guys think about it ?