v2_0_cpp_adaptor
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 | |
| T[] | array | since 2.0.0 |
| char[] | str | since 2.0.0 |
| unsigned char[] | bin | since 2.0.0 |
| std::list | array | |
| std::map | array | |
| std::pair | array | |
| std::set | array | |
| std::string | str | |
| std::wstring | array of positive integer | |
| std::vector | array | |
| std::vector<char> | bin | |
| std::vector<unsigned char> | bin | since 1.2.0 |
| msgpack type | msgpack::object type | note |
|---|---|---|
| nil_t | nil | |
| tuple | array | |
| array_ref *3 | array | since 1.2.0 |
| raw_ref *3 | bin | |
| v4raw_ref *3 | str | since 1.2.0 |
| ext | ext | *4 |
| ext_ref *3 | ext | *4 |
| variant | any type | since 1.2.0, boost is required |
| 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 |
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.
*4 ext with maximum size limitation On 64bit environment, there is no limitation. But on 32bit environment, ext32 with maximum size is not supported. See https://github.com/msgpack/msgpack-c/pull/175#issue-51915694
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 map, otherwise adapted to array.
#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/cpp_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 map, otherwise adapted to array.
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)); };
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/cpp_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; } };
accessing private members
non-intrusive approach can't access the target class's private members because of non-intrusive. In order to access private members from adaptor class template specializations, you need to define friend class template declaration in the target class (intrusively) as follows:
class my_class { // ... template <typename T, typename Enabler> friend struct msgpack::MSGPACK_DEFAULT_API_NS::adaptor::pack; template <typename T, typename Enabler> friend struct msgpack::MSGPACK_DEFAULT_API_NS::adaptor::convert; template <typename T, typename Enabler> friend struct msgpack::MSGPACK_DEFAULT_API_NS::adaptor::object; template <typename T, typename Enabler> friend struct msgpack::MSGPACK_DEFAULT_API_NS::adaptor::object_with_zone; template <typename T, typename Enabler> friend struct msgpack::MSGPACK_DEFAULT_API_NS::adaptor::as; // ... };
Before the version 4.0.3, you need to declare for object adaptor as follows:
template <typename T, typename Enabler, typename Enabler2> friend struct msgpack::MSGPACK_DEFAULT_API_NS::adaptor::object;
You need to declare only the class template specializations that you defined.
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.