feat: introduce support for struct-as-dict serialization (#459)

This extends the functionality of SDBUSCPP_REGISTER_STRUCT macro.

It now provides functionality for serializing a user-defined struct as an a{sv} dictionary, and for deserializing an a{sv} dictionary into a user-defined struct. The former one is achieved by decorating the struct with sdbus::as_dictionary(struct), the latter one is an automatic behavior -- when sdbus-c++ is asked to deserialize into a struct but the data in the message is of type a{sv}, then the dict-to-struct deserialization is performed automatically.

There are some aspects of behavior in the serialization/deserialization functionality that can be customized by the client. Newly introduced SDBUSCPP_ENABLE_NESTED_STRUCT2DICT_SERIALIZATION and SDBUSCPP_ENABLE_RELAXED_DICT2STRUCT_DESERIALIZATION macros serve the purpose.
This commit is contained in:
Stanislav Angelovič
2024-11-20 23:25:13 +01:00
committed by GitHub
parent 8a117f8b42
commit 02ca7212d1
9 changed files with 501 additions and 136 deletions
+59 -39
View File
@@ -114,6 +114,8 @@ namespace sdbus {
#endif
template <typename _Enum, typename = std::enable_if_t<std::is_enum_v<_Enum>>>
Message& operator<<(const _Enum& item);
template <typename _Key, typename _Value>
Message& operator<<(const DictEntry<_Key, _Value>& value);
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
Message& operator<<(const std::map<_Key, _Value, _Compare, _Allocator>& items);
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
@@ -150,6 +152,8 @@ namespace sdbus {
#endif
template <typename _Enum, typename = std::enable_if_t<std::is_enum_v<_Enum>>>
Message& operator>>(_Enum& item);
template <typename _Key, typename _Value>
Message& operator>>(DictEntry<_Key, _Value>& value);
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
Message& operator>>(std::map<_Key, _Value, _Compare, _Allocator>& items);
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
@@ -196,6 +200,13 @@ namespace sdbus {
Message& appendArray(char type, const void *ptr, size_t size);
Message& readArray(char type, const void **ptr, size_t *size);
template <typename _Key, typename _Value, typename _Callback>
Message& serializeDictionary(const _Callback& callback);
template <typename _Key, typename _Value>
Message& serializeDictionary(const std::initializer_list<DictEntry<_Key, _Value>>& dictEntries);
template <typename _Key, typename _Value, typename _Callback>
Message& deserializeDictionary(const _Callback& callback);
explicit operator bool() const;
void clearFlags();
@@ -238,11 +249,6 @@ namespace sdbus {
template <typename _Element, typename _Allocator>
void deserializeArraySlow(std::vector<_Element, _Allocator>& items);
template <typename _Dictionary>
void serializeDictionary(const _Dictionary& items);
template <typename _Dictionary>
void deserializeDictionary(_Dictionary& items);
protected:
Message() = default;
explicit Message(internal::IConnection* connection) noexcept;
@@ -402,10 +408,21 @@ namespace sdbus {
}
}
template <typename _Key, typename _Value>
inline Message& Message::operator<<(const DictEntry<_Key, _Value>& value)
{
openDictEntry<_Key, _Value>();
*this << value.first;
*this << value.second;
closeDictEntry();
return *this;
}
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
inline Message& Message::operator<<(const std::map<_Key, _Value, _Compare, _Allocator>& items)
{
serializeDictionary(items);
serializeDictionary<_Key, _Value>([&items](Message& msg){ for (const auto& item : items) msg << item; });
return *this;
}
@@ -413,28 +430,27 @@ namespace sdbus {
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
inline Message& Message::operator<<(const std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items)
{
serializeDictionary(items);
serializeDictionary<_Key, _Value>([&items](Message& msg){ for (const auto& item : items) msg << item; });
return *this;
}
template <typename _Dictionary>
inline void Message::serializeDictionary(const _Dictionary& items)
template <typename _Key, typename _Value>
inline Message& Message::serializeDictionary(const std::initializer_list<DictEntry<_Key, _Value>>& items)
{
using KeyType = typename _Dictionary::key_type;
using MappedType = typename _Dictionary::mapped_type;
serializeDictionary<_Key, _Value>([&](Message& msg){ for (const auto& item : items) msg << item; });
openContainer<DictEntry<KeyType, MappedType>>();
for (const auto& item : items)
{
openDictEntry<KeyType, MappedType>();
*this << item.first;
*this << item.second;
closeDictEntry();
}
return *this;
}
template <typename _Key, typename _Value, typename _Callback>
inline Message& Message::serializeDictionary(const _Callback& callback)
{
openContainer<DictEntry<_Key, _Value>>();
callback(*this);
closeContainer();
return *this;
}
namespace detail
@@ -619,10 +635,21 @@ namespace sdbus {
exitContainer();
}
template <typename _Key, typename _Value>
inline Message& Message::operator>>(DictEntry<_Key, _Value>& value)
{
if (!enterDictEntry<_Key, _Value>())
return *this;
*this >> value.first >> value.second;
exitDictEntry();
return *this;
}
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
inline Message& Message::operator>>(std::map<_Key, _Value, _Compare, _Allocator>& items)
{
deserializeDictionary(items);
deserializeDictionary<_Key, _Value>([&items](auto dictEntry){ items.insert(std::move(dictEntry)); });
return *this;
}
@@ -630,37 +657,30 @@ namespace sdbus {
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
inline Message& Message::operator>>(std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items)
{
deserializeDictionary(items);
deserializeDictionary<_Key, _Value>([&items](auto dictEntry){ items.insert(std::move(dictEntry)); });
return *this;
}
template <typename _Dictionary>
inline void Message::deserializeDictionary(_Dictionary& items)
template <typename _Key, typename _Value, typename _Callback>
inline Message& Message::deserializeDictionary(const _Callback& callback)
{
using KeyType = typename _Dictionary::key_type;
using MappedType = typename _Dictionary::mapped_type;
if (!enterContainer<DictEntry<KeyType, MappedType>>())
return;
if (!enterContainer<DictEntry<_Key, _Value>>())
return *this;
while (true)
{
if (!enterDictEntry<KeyType, MappedType>())
DictEntry<_Key, _Value> dictEntry;
*this >> dictEntry;
if (!*this)
break;
KeyType key;
MappedType value;
*this >> key >> value;
items.emplace(std::move(key), std::move(value));
exitDictEntry();
callback(std::move(dictEntry));
}
clearFlags();
exitContainer();
return *this;
}
namespace detail
+33 -3
View File
@@ -273,6 +273,7 @@ namespace sdbus {
{
static constexpr std::array contents = (signature_of_v<_ValueTypes> + ...);
static constexpr std::array value = std::array{'('} + contents + std::array{')'};
static constexpr char type_value{'r'}; /* Not actually used in signatures on D-Bus, see specs */
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
};
@@ -317,6 +318,7 @@ namespace sdbus {
struct signature_of<DictEntry<_T1, _T2>>
{
static constexpr std::array value = std::array{'{'} + signature_of_v<std::tuple<_T1, _T2>> + std::array{'}'};
static constexpr char type_value{'e'}; /* Not actually used in signatures on D-Bus, see specs */
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
};
@@ -349,9 +351,7 @@ namespace sdbus {
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
struct signature_of<std::map<_Key, _Value, _Compare, _Allocator>>
{
static constexpr std::array contents = signature_of_v<std::tuple<_Key, _Value>>;
static constexpr std::array dict_entry = std::array{'{'} + contents + std::array{'}'};
static constexpr std::array value = std::array{'a'} + dict_entry;
static constexpr std::array value = std::array{'a'} + signature_of_v<DictEntry<_Key, _Value>>;
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
};
@@ -575,6 +575,36 @@ namespace sdbus {
constexpr bool is_one_of_variants_types<std::variant<_VariantTypes...>, _QueriedType>
= (std::is_same_v<_QueriedType, _VariantTypes> || ...);
// Wrapper (tag) denoting we want to serialize user-defined struct
// into a D-Bus message as a dictionary of strings to variants.
template <typename _Struct>
struct as_dictionary
{
explicit as_dictionary(const _Struct& s) : m_struct(s) {}
const _Struct& m_struct;
};
template <typename _Type>
const _Type& as_dictionary_if_struct(const _Type& object)
{
return object; // identity in case _Type is not struct (user-defined structs shall provide an overload)
}
// By default, the dict-as-struct deserialization strategy is strict.
// Strict means that every key of the deserialized dictionary must have its counterpart member in the struct, otherwise an exception is thrown.
// Relaxed means that a key that does not have a matching struct member is silently ignored.
// The behavior can be overridden for user-defined struct by specializing this variable template.
template <typename _Struct>
constexpr auto strict_dict_as_struct_deserialization_v = true;
// By default, the struct-as-dict serialization strategy is single-level only (as opposed to nested).
// Single-level means that only the specific struct is serialized as a dictionary, serializing members that are structs always as structs.
// Nested means that the struct *and* its members that are structs are all serialized as a dictionary. If nested strategy is also
// defined for the nested struct, then the same behavior applies for that struct, recursively.
// The behavior can be overridden for user-defined struct by specializing this variable template.
template <typename _Struct>
constexpr auto nested_struct_as_dict_serialization_v = false;
namespace detail
{
template <class _Function, class _Tuple, typename... _Args, std::size_t... _I>
+142 -59
View File
@@ -31,6 +31,7 @@
#include <sdbus-c++/TypeTraits.h>
#include <cstring>
#include <cstddef>
#include <memory>
#include <string>
#include <tuple>
@@ -66,6 +67,15 @@ namespace sdbus {
msg_.seal();
}
template <typename _Struct>
explicit Variant(const as_dictionary<_Struct>& value) : Variant()
{
msg_.openVariant<std::map<std::string, Variant>>();
msg_ << as_dictionary(value.m_struct);
msg_.closeVariant();
msg_.seal();
}
template <typename... _Elements>
Variant(const std::variant<_Elements...>& value)
: Variant()
@@ -77,10 +87,10 @@ namespace sdbus {
template <typename _ValueType>
_ValueType get() const
{
_ValueType val;
msg_.rewind(false);
msg_.enterVariant<_ValueType>();
_ValueType val;
msg_ >> val;
msg_.exitVariant();
return val;
@@ -405,6 +415,9 @@ struct std::tuple_size<sdbus::Struct<_ValueTypes...>>
* clients to use their struct conveniently instead of the (too
* generic and less expressive) `sdbus::Struct<...>` in sdbus-c++ API.
*
* It also enables to serialize a user-defined struct as an a{sv} dictionary,
* and to deserialize an a{sv} dictionary into the user-defined struct.
*
* The first argument is the struct type name and the remaining arguments
* are names of struct members. Members must be of types supported by
* sdbus-c++ (or of user-defined types that sdbus-c++ was taught to support).
@@ -426,31 +439,96 @@ struct std::tuple_size<sdbus::Struct<_ValueTypes...>>
*
* SDBUSCPP_REGISTER_STRUCT(foo::ABC, number, name, data);
*
* The macro effectively generates the `sdbus::Message` serialization
* and deserialization operators and the `sdbus::signature_of`
* specialization for `foo::ABC`.
*
* Up to 16 struct members are supported by the macro.
*
***********************************************/
#define SDBUSCPP_REGISTER_STRUCT(STRUCT, ...) \
namespace sdbus { \
static_assert(SDBUSCPP_PP_NARG(__VA_ARGS__) <= 16, \
"Not more than 16 struct members are supported, please open an issue if you need more"); \
inline sdbus::Message& operator<<(sdbus::Message& msg, const STRUCT& items) \
{ \
return msg << sdbus::Struct{std::forward_as_tuple(SDBUSCPP_STRUCT_MEMBERS(items, __VA_ARGS__))}; \
} \
inline sdbus::Message& operator>>(sdbus::Message& msg, STRUCT& items) \
{ \
sdbus::Struct s{std::forward_as_tuple(SDBUSCPP_STRUCT_MEMBERS(items, __VA_ARGS__))}; \
return msg >> s; \
} \
template <> \
struct signature_of<STRUCT> \
: signature_of<sdbus::Struct<SDBUSCPP_STRUCT_MEMBER_TYPES(STRUCT, __VA_ARGS__)>> \
{}; \
} \
#define SDBUSCPP_REGISTER_STRUCT(STRUCT, ...) \
namespace sdbus { \
static_assert(SDBUSCPP_PP_NARG(__VA_ARGS__) <= 16, \
"Not more than 16 struct members are supported, please open an issue if you need more"); \
\
template <> \
struct signature_of<STRUCT> \
: signature_of<sdbus::Struct<SDBUSCPP_STRUCT_MEMBER_TYPES(STRUCT, __VA_ARGS__)>> \
{}; \
\
inline auto as_dictionary_if_struct(const STRUCT& object) \
{ \
return as_dictionary<STRUCT>(object); \
} \
\
inline sdbus::Message& operator<<(sdbus::Message& msg, const STRUCT& items) \
{ \
return msg << sdbus::Struct{std::forward_as_tuple(SDBUSCPP_STRUCT_MEMBERS(items, __VA_ARGS__))}; \
} \
\
inline Message& operator<<(Message& msg, const as_dictionary<STRUCT>& s) \
{ \
if constexpr (!nested_struct_as_dict_serialization_v<STRUCT>) \
return msg.serializeDictionary<std::string, Variant>({SDBUSCPP_STRUCT_MEMBERS_AS_DICT_ENTRIES(s.m_struct, __VA_ARGS__)}); \
else \
return msg.serializeDictionary<std::string, Variant>({SDBUSCPP_STRUCT_MEMBERS_AS_NESTED_DICT_ENTRIES(s.m_struct, __VA_ARGS__)}); \
} \
\
inline Message& operator>>(Message& msg, STRUCT& s) \
{ \
/* First, try to deserialize as a struct */ \
if (msg.peekType().first == signature_of<STRUCT>::type_value) \
{ \
Struct sdbusStruct{std::forward_as_tuple(SDBUSCPP_STRUCT_MEMBERS(s, __VA_ARGS__))}; \
return msg >> sdbusStruct; \
} \
\
/* Otherwise try to deserialize as a dictionary of strings to variants */ \
\
return msg.deserializeDictionary<std::string, Variant>([&s](const auto& dictEntry) \
{ \
const std::string& key = dictEntry.first; /* Intentionally not using structured bindings */ \
const Variant& value = dictEntry.second; \
\
using namespace std::string_literals; \
/* This also handles members which are structs serialized as dict of strings to variants, recursively */ \
SDBUSCPP_FIND_AND_DESERIALIZE_STRUCT_MEMBERS(s, __VA_ARGS__) \
SDBUS_THROW_ERROR_IF( strict_dict_as_struct_deserialization_v<STRUCT> \
, ("Failed to deserialize struct from a dictionary: could not find field '"s += key) += "' in struct 'my::Struct'" \
, EINVAL ); \
}); \
} \
} \
/**/
/********************************************//**
* @name SDBUSCPP_ENABLE_RELAXED_DICT2STRUCT_DESERIALIZATION
*
* Enables relaxed deserialization of an a{sv} dictionary into a user-defined struct STRUCT.
*
* The default (strict) deserialization mode is that if there are entries in the dictionary
* which do not have a corresponding field in the struct, an exception is thrown.
* In the relaxed mode, such entries are silently skipped.
*
* The macro can only be used in combination with SDBUSCPP_REGISTER_STRUCT macro,
* and must be placed before SDBUSCPP_REGISTER_STRUCT macro.
***********************************************/
#define SDBUSCPP_ENABLE_RELAXED_DICT2STRUCT_DESERIALIZATION(STRUCT) \
template <> \
constexpr auto sdbus::strict_dict_as_struct_deserialization_v<STRUCT> = false; \
/**/
/********************************************//**
* @name SDBUSCPP_ENABLE_NESTED_STRUCT2DICT_SERIALIZATION
*
* Enables nested serialization of user-defined struct STRUCT as an a{sv} dictionary.
*
* By default, STRUCT fields which are structs themselves are serialized as D-Bus structs.
* This macro tells sdbus-c++ to also serialize nested structs, in a recursive fashion,
* as a{sv} dictionaries.
*
* The macro can only be used in combination with SDBUSCPP_REGISTER_STRUCT macro,
* and must be placed before SDBUSCPP_REGISTER_STRUCT macro.
***********************************************/
#define SDBUSCPP_ENABLE_NESTED_STRUCT2DICT_SERIALIZATION(STRUCT) \
template <> \
constexpr auto sdbus::nested_struct_as_dict_serialization_v<STRUCT> = true \
/**/
/*!
@@ -458,49 +536,54 @@ struct std::tuple_size<sdbus::Struct<_ValueTypes...>>
*
* Internal helper preprocessor facilities
*/
#define SDBUSCPP_STRUCT_MEMBERS(STRUCT, ...) \
SDBUSCPP_PP_CAT(SDBUSCPP_STRUCT_MEMBERS_, SDBUSCPP_PP_NARG(__VA_ARGS__))(STRUCT, __VA_ARGS__) \
#define SDBUSCPP_STRUCT_MEMBERS(STRUCT, ...) \
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_STRUCT_MEMBER, SDBUSCPP_PP_COMMA, STRUCT, __VA_ARGS__) \
/**/
#define SDBUSCPP_STRUCT_MEMBERS_1(S, M1) S.M1
#define SDBUSCPP_STRUCT_MEMBERS_2(S, M1, M2) S.M1, S.M2
#define SDBUSCPP_STRUCT_MEMBERS_3(S, M1, M2, M3) S.M1, S.M2, S.M3
#define SDBUSCPP_STRUCT_MEMBERS_4(S, M1, M2, M3, M4) S.M1, S.M2, S.M3, S.M4
#define SDBUSCPP_STRUCT_MEMBERS_5(S, M1, M2, M3, M4, M5) S.M1, S.M2, S.M3, S.M4, S.M5
#define SDBUSCPP_STRUCT_MEMBERS_6(S, M1, M2, M3, M4, M5, M6) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6
#define SDBUSCPP_STRUCT_MEMBERS_7(S, M1, M2, M3, M4, M5, M6, M7) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7
#define SDBUSCPP_STRUCT_MEMBERS_8(S, M1, M2, M3, M4, M5, M6, M7, M8) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8
#define SDBUSCPP_STRUCT_MEMBERS_9(S, M1, M2, M3, M4, M5, M6, M7, M8, M9) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9
#define SDBUSCPP_STRUCT_MEMBERS_10(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10
#define SDBUSCPP_STRUCT_MEMBERS_11(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11
#define SDBUSCPP_STRUCT_MEMBERS_12(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12
#define SDBUSCPP_STRUCT_MEMBERS_13(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12, S.M13
#define SDBUSCPP_STRUCT_MEMBERS_14(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12, S.M13, S.M14
#define SDBUSCPP_STRUCT_MEMBERS_15(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12, S.M13, S.M14, S.M15
#define SDBUSCPP_STRUCT_MEMBERS_16(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15, M16) S.M1, S.M2, S.M3, S.M4, S.M5, S.M6, S.M7, S.M8, S.M9, S.M10, S.M11, S.M12, S.M13, S.M14, S.M15, S.M16
#define SDBUSCPP_STRUCT_MEMBER(STRUCT, MEMBER) STRUCT.MEMBER
#define SDBUSCPP_STRUCT_MEMBER_TYPES(STRUCT, ...) \
SDBUSCPP_PP_CAT(SDBUSCPP_STRUCT_MEMBER_TYPES_, SDBUSCPP_PP_NARG(__VA_ARGS__))(STRUCT, __VA_ARGS__) \
#define SDBUSCPP_STRUCT_MEMBER_TYPES(STRUCT, ...) \
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_STRUCT_MEMBER_TYPE, SDBUSCPP_PP_COMMA, STRUCT, __VA_ARGS__) \
/**/
#define SDBUSCPP_STRUCT_MEMBER_TYPES_1(S, M1) decltype(S::M1)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_2(S, M1, M2) decltype(S::M1), decltype(S::M2)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_3(S, M1, M2, M3) decltype(S::M1), decltype(S::M2), decltype(S::M3)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_4(S, M1, M2, M3, M4) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_5(S, M1, M2, M3, M4, M5) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_6(S, M1, M2, M3, M4, M5, M6) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_7(S, M1, M2, M3, M4, M5, M6, M7) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_8(S, M1, M2, M3, M4, M5, M6, M7, M8) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_9(S, M1, M2, M3, M4, M5, M6, M7, M8, M9) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_10(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_11(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_12(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_13(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12), decltype(S::M13)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_14(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12), decltype(S::M13), decltype(S::M14)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_15(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12), decltype(S::M13), decltype(S::M14), decltype(S::M15)
#define SDBUSCPP_STRUCT_MEMBER_TYPES_16(S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15, M16) decltype(S::M1), decltype(S::M2), decltype(S::M3), decltype(S::M4), decltype(S::M5), decltype(S::M6), decltype(S::M7), decltype(S::M8), decltype(S::M9), decltype(S::M10), decltype(S::M11), decltype(S::M12), decltype(S::M13), decltype(S::M14), decltype(S::M15), decltype(S::M16)
#define SDBUSCPP_STRUCT_MEMBER_TYPE(STRUCT, MEMBER) decltype(STRUCT::MEMBER)
#define SDBUSCPP_STRUCT_MEMBERS_AS_DICT_ENTRIES(STRUCT, ...) \
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_STRUCT_MEMBER_AS_DICT_ENTRY, SDBUSCPP_PP_COMMA, STRUCT, __VA_ARGS__) \
/**/
#define SDBUSCPP_STRUCT_MEMBER_AS_DICT_ENTRY(STRUCT, MEMBER) {#MEMBER, Variant{STRUCT.MEMBER}}
#define SDBUSCPP_STRUCT_MEMBERS_AS_NESTED_DICT_ENTRIES(STRUCT, ...) \
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_STRUCT_MEMBER_AS_NESTED_DICT_ENTRY, SDBUSCPP_PP_COMMA, STRUCT, __VA_ARGS__) \
/**/
#define SDBUSCPP_STRUCT_MEMBER_AS_NESTED_DICT_ENTRY(STRUCT, MEMBER) {#MEMBER, Variant{as_dictionary_if_struct(STRUCT.MEMBER)}}
#define SDBUSCPP_FIND_AND_DESERIALIZE_STRUCT_MEMBERS(STRUCT, ...) \
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_FIND_AND_DESERIALIZE_STRUCT_MEMBER, SDBUSCPP_PP_SPACE, STRUCT, __VA_ARGS__) \
/**/
#define SDBUSCPP_FIND_AND_DESERIALIZE_STRUCT_MEMBER(STRUCT, MEMBER) if (key == #MEMBER) STRUCT.MEMBER = value.get<decltype(STRUCT.MEMBER)>(); else
#define SDBUSCPP_FOR_EACH_1(M, D, S, M1) M(S, M1)
#define SDBUSCPP_FOR_EACH_2(M, D, S, M1, M2) M(S, M1) D M(S, M2)
#define SDBUSCPP_FOR_EACH_3(M, D, S, M1, M2, M3) M(S, M1) D M(S, M2) D M(S, M3)
#define SDBUSCPP_FOR_EACH_4(M, D, S, M1, M2, M3, M4) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4)
#define SDBUSCPP_FOR_EACH_5(M, D, S, M1, M2, M3, M4, M5) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5)
#define SDBUSCPP_FOR_EACH_6(M, D, S, M1, M2, M3, M4, M5, M6) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6)
#define SDBUSCPP_FOR_EACH_7(M, D, S, M1, M2, M3, M4, M5, M6, M7) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7)
#define SDBUSCPP_FOR_EACH_8(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8)
#define SDBUSCPP_FOR_EACH_9(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9)
#define SDBUSCPP_FOR_EACH_10(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10)
#define SDBUSCPP_FOR_EACH_11(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11)
#define SDBUSCPP_FOR_EACH_12(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12)
#define SDBUSCPP_FOR_EACH_13(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12) D M(S, M13)
#define SDBUSCPP_FOR_EACH_14(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12) D M(S, M13) D M(S, M14)
#define SDBUSCPP_FOR_EACH_15(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12) D M(S, M13) D M(S, M14) D M(S, M15)
#define SDBUSCPP_FOR_EACH_16(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15, M16) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12) D M(S, M13) D M(S, M14) D M(S, M15) D M(S, M16)
#define SDBUSCPP_PP_CAT(X, Y) SDBUSCPP_PP_CAT_IMPL(X, Y)
#define SDBUSCPP_PP_CAT_IMPL(X, Y) X##Y
#define SDBUSCPP_PP_NARG(...) SDBUSCPP_PP_NARG_IMPL(__VA_ARGS__, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define SDBUSCPP_PP_NARG_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _N, ...) _N
#define SDBUSCPP_PP_COMMA ,
#define SDBUSCPP_PP_SPACE
#endif /* SDBUS_CXX_TYPES_H_ */