Skip to content

v2_0_cpp_adaptor

Takatoshi Kondo edited this page May 4, 2017 · 24 revisions

adaptors

When you want to pack to msgpack::object from a various type object, you need an adaptor. Converting to msgpack::object from a various type object and vice versa, it requires an adaptor too.

See conversion.

predefined adaptors

msgpack-c provides predefined adaptors for C++ primitive types and standard libraries.

C++ type msgpack::object type note
bool bool
char* str
std::deque array
char positive/negative integer
signed ints *1 positive/negative integer
unsigned ints *2 positive integer
float float32 since 2.1.0
double float64
std::list array
std::map array
std::pair array
std::set array
std::string str
std::vector array
std::vector<char> bin
std::vector<unsigned char> bin since 1.2.0
msgpack type msgpack::object type note
array_ref *3 array since 1.2.0
raw_ref *3 bin
v4raw_ref *3 str since 1.2.0
ext ext
ext_ref *3 ext
C++11 type msgpack::object type note
std::array array
std::array<char> bin
std::array<unsigned char> bin since 1.2.0
std::forward_list array
std::tuple array
std::unordered_map array
std::unordered_set array
std::unique_ptr depends on template type since 1.2.0
std::shared_ptr depends on template type since 1.2.0
boost type msgpack::object type note
string_ref str since 1.2.0
string_view str since 2.1.0
optional depends on optional type since 1.2.0
fusion sequence array since 1.2.0
variant all types since 1.2.0

When you use the adaptors for boost containers, you need to define MSGPACK_USE_BOOST.

*1 signed ints signed char, signed short, signed int, signed long, signed long long

*2 unsigned ints unsigned char, unsigned short, unsigned int, signed long, signed long long

*3 xxx_ref types enforce packing types without copies.

msgpack::object type is defined as follows: https://github.com/msgpack/msgpack/blob/master/spec.md

These adaptors are defined in the following directory: https://github.com/msgpack/msgpack-c/tree/master/include/msgpack/adaptor

defining custom adaptors

classes

intrusive approach

When you want to adapt your class to msgpack, use MSGPACK_DEFINE macro.

macro msgpack::object type note
MSGPACK_DEFINE array or map *3
MSGPACK_DEFINE_ARRAY array since 1.2.0
MSGPACK_DEFINE_MAP map since 1.2.0

*3 When you define MSGPACK_USE_DEFINE_MAP, MSGPACK_DEFINE is adapted to array, otherwise adapted to map.

#include <msgpack.hpp>

struct your_class {
    int a;
    std::string b;
    MSGPACK_DEFINE(a, b);
};

// ...

Let's say a == 42, b == "ABC", the object of your_class is serialized as follows:

// array
[42,"ABC"]
// map (MSGPACK_USE_DEFINE_MAP is defined)
{"a":42,"b":"ABC"}

I use JSON to help understanding. But actual condition is msgpack format.

The macro MSGPACK_DEFINE provides packing, converting to msgpack object with zone, and converting to your_class from msgpack::object functionalities. your_class is packed/converted as msgpack array.

https://github.com/msgpack/msgpack-c/blob/master/example/cpp03/class_intrusive.cpp

When you want to adapt your class with its base classes ,use MSGPACK_BASE macro.

#include <msgpack.hpp>
struct base1 {
   int a;
   MSGPACK_DEFINE(a);
};
struct base2 {
   int a;
   MSGPACK_DEFINE(a);
};
struct your_class : base1, base2 {
    int a;
    std::string b;
    // You can choose any order. It is represented to the msgpack array elements order.
    MSGPACK_DEFINE(b, MSGPACK_BASE(base2), a, MSGPACK_BASE(base1));
};

// ...
macro msgpack::object type
MSGPACK_BASE array or map *4
MSGPACK_BASE_ARRAY array
MSGPACK_BASE_MAP map

*4 When you define MSGPACK_USE_DEFINE_MAP, MSGPACK_BASE is adapted to array, otherwise adapted to map.

You must use MSGPACK_BASE with MSGPACK_DEFINE, MSGPACK_BASE_ARRAY with MSGPACK_DEFINE_ARRAY, or MSGPACK_BASE_MAP with MSGPACK_DEFINE_MAP. When you use MSGPACK_BASE_MAP, the key is the STR of the base class name.

Let's say base1::a == 1, base2::a == 2, your_class::a == 42, and your_class::b == "ABC", the object of your_class is serialized as follows:

// array
["ABC",[2],42,[1]]
// map (MSGPACK_USE_DEFINE_MAP is defined)
{"b":"ABC","base2":{"a":2},"a":42,"base1":{"a":1}}

I use JSON to help understanding. But actual condition is msgpack format.

When you use an array based adaptor, e.g.)MSGPACK_DEFINE_ARRAY, the order of member variables and base classes is important. The index of the msgpack::object array is corresponding to the member variables order. When you use a map based adaptor, e.g.)MSGPACK_DEFINE_MAP, the names of member variables and base classes is important. The keys of the msgpack::object map is corresponding to member variables names and the base classes names.

since 2.1.0

You can also customize the name of the map key using MSGPACK_NVP. For example, the key of the value b is #b. You can use any strings even if it can't use as C++ variable name. It helps inter programming language data transportation.

#include <msgpack.hpp>

struct your_class {
    int a;
    std::string b;
    MSGPACK_DEFINE_MAP(a, MSGPACK_NVP("#b", b));
};
{"a":42,"#b":"ABC"}

non-intrusive approach

When you don't want to modify your class, you can use non-intrusive approach. msgpack-c provides the following four functor class templates. Those templates are in the namespace msgpack::adaptor.

convert

Converting from msgpack::object to T.

template <typename T>
struct convert {
    msgpack::object const& operator()(msgpack::object const&, T&) const;
};
pack

Packing from T into msgpack::packer.

template <typename T>
struct pack {
    template <typename Stream>
    msgpack::packer<Stream>& operator()(msgpack::packer<Stream>&, T const&) const;
};
object

Set msgpack::object by T.

template <typename T>
struct object {
    void operator()(msgpack::object&, T const&) const;
};
object_with_zone
  • Set msgpack::object::with_zone by T.
template <typename T>
struct object_with_zone {
    void operator()(msgpack::object::with_zone&, T const&) const;
};

In order to add packing/conversion supports for your custom types, you need to specialize the templates.

Here are templates that are specialized to 'your_type'.

template <>
struct convert<your_type> {
    msgpack::object const& operator()(msgpack::object const&, your_type&) const {
        // your implementation
    }
};
template <>
struct pack<your_type> {
    template <typename Stream>
    msgpack::packer<Stream>& operator()(msgpack::packer<Stream>&, your_type const&) const {
        // your implementation
    }
};
template <>
struct object<your_type> {
    void operator()(msgpack::object&, your_type const&) const {
        // your implementation
    }
};
template <>
struct object_with_zone<your_type> {
    void operator()(msgpack::object::with_zone&, your_type const&) const {
        // your implementation
    }
};

You don't need to specialize all the templates. When you want to support pack only, then you can specialze only the pack template.

Example:

#include <msgpack.hpp>

class my_class {
public:
    my_class() {} // When you want to convert from msgpack::object to my_class
                  // using msgpack::object::as fucntion template,
                  // my_class should be default constructible.
    my_class(std::string const& name, int age):name_(name), age_(age) {}

    // my_class should provide getters for the data members you want to pack.
    std::string const& get_name() const { return name_; }
    int get_age() const { return age_; }
private:
    std::string name_;
    int age_;
};

namespace msgpack {
MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) {
namespace adaptor {

// Place class template specialization here
template<>
struct convert<my_class> {
    msgpack::object const& operator()(msgpack::object const& o, my_class& v) const {
        if (o.type != msgpack::type::ARRAY) throw msgpack::type_error();
        if (o.via.array.size != 2) throw msgpack::type_error();
        v = my_class(
            o.via.array.ptr[0].as<std::string>(),
            o.via.array.ptr[1].as<int>());
        return o;
    }
};

template<>
struct pack<my_class> {
    template <typename Stream>
    packer<Stream>& operator()(msgpack::packer<Stream>& o, my_class const& v) const {
        // packing member variables as an array.
        o.pack_array(2);
        o.pack(v.get_name());
        o.pack(v.get_age());
        return o;
    }
};

template <>
struct object_with_zone<my_class> {
    void operator()(msgpack::object::with_zone& o, my_class const& v) const {
        o.type = type::ARRAY;
        o.via.array.size = 2;
        o.via.array.ptr = static_cast<msgpack::object*>(
            o.zone.allocate_align(sizeof(msgpack::object) * o.via.array.size));
        o.via.array.ptr[0] = msgpack::object(v.get_name(), o.zone);
        o.via.array.ptr[1] = msgpack::object(v.get_age(), o.zone);
    }
};

} // namespace adaptor
} // MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS)
} // namespace msgpack

https://github.com/msgpack/msgpack-c/blob/master/example/cpp03/class_non_intrusive.cpp

When you use msgpack::object::as member function template in convert class template specilization, , temporary objects are created.

template<>
struct convert<my_class> {
    msgpack::object const& operator()(msgpack::object const& o, my_class& v) const {
        if (o.type != msgpack::type::ARRAY) throw msgpack::type_error();
        if (o.via.array.size != 2) throw msgpack::type_error();
        v = my_class(
            o.via.array.ptr[0].as<std::string>(), // temporary object is created here
            o.via.array.ptr[1].as<int>());
        return o;
    }
};

If you can get the reference of the member variables in converting target class, you can remove the temporary object creation. For example, if member variables of my_class are public, you can apply operator>> to them as follows:

class my_class {
public:
    /* ... */
    std::string name_;
    int age_;
};

template<>
struct convert<my_class> {
    msgpack::object const& operator()(msgpack::object const& o, my_class& v) const {
        if (o.type != msgpack::type::ARRAY) throw msgpack::type_error();
        if (o.via.array.size != 2) throw msgpack::type_error();
        o.via.array.ptr[0] >> v.name_; // no temporary object creation
        o.via.array.ptr[1] >> v.age_;
        return o;
    }
};

non default constructible class support (C++11 only, since 1.2.0)

You might want to convert to a class that doesn't have default constructor from a msgpack::object. In order to do that, you can use 'as' class template specialization.

Here is a non default constructible class:

struct no_def_con {
    no_def_con() = delete;
    no_def_con(int i):i(i) {}
    int i;
    MSGPACK_DEFINE(i);
};

Define 'as' class template specialization as follows:

namespace msgpack {
MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) {
namespace adaptor {
template <>
struct as<no_def_con> {
    no_def_con operator()(msgpack::object const& o) const {
        if (o.type != msgpack::type::ARRAY) throw msgpack::type_error();
        if (o.via.array.size != 1) throw msgpack::type_error();
        return no_def_con(o.via.array.ptr[0].as<int>());
    }
};
} // adaptor
} // MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS)
} // msgpack

Then, you can convert to your class form msgpack::object as follows:

   msgpack::object o = /*...*/
   no_def_con ndc = o.as<no_def_con>();

NOTE: MSVC2015 doesn't support C++11 feature completely. This feature isn't supported on MSVC2015 due to lack of Expression SFINAE. See https://github.com/msgpack/msgpack-c/issues/343#issuecomment-131654386.

enums

When you want to adapt enum or enum class to msgpack, use MSGPACK_ADD_ENUM macro.

#include <msgpack.hpp>

enum your_enum {
    elem1,
    elem2
};

MSGPACK_ADD_ENUM(your_enum);

enum class your_enum_class {
    elem1,
    elem2
};

MSGPACK_ADD_ENUM(your_enum_class);

// ...

You need to use MSGPACK_ADD_ENUM in the global namespace.

The macro MSGPACK_DEFINE provides packing, converting to msgpack object with or without zone, and converting to your_enum from msgpack::object functionalities. your_enum is packed/converted as msgpack positive/negative integer.

See an example.