boost::pfr 实现了更优雅的 get_name,底层原理依然是解析
__PRETTY_FUNCTION__
。本文提到的方法意义不大了。
最近使用 FlatBuffers 作为 RPC 的序列化协议时,遇到一些问题:
仔细思考了下,当前的项目并不需要支持跨语言的 RPC 调用,只需要处理 C++ 中的序列化/反序列化;易用性反而是需求的重点,各类自定义的数据结构都希望可以方便的进行序列化/反序列化而不需要手动的进行转换。在参考了部分开源项目的思路后,决定抛弃 FlatBuffers、使用 C++ 原生数据结构 + 宏的方式定义类型 Schema、完成序列化/反序列化。
静态反射的实现参考 garbageslam/visit_struct 项目中侵入式定义,它的核心原理是函数的可见范围,及派生类向基类的自动类型转换。举个例子(在线执行):
#include <algorithm>
#include <iostream>
#include <string_view>
#include <tuple>
#include <type_traits>
namespace reflection {
template <class List, class T>
struct Append;
template <class... Ts, class T>
struct Append<std::tuple<Ts...>, T> {
using type = std::tuple<Ts..., T>;
};
template <int N = 64>
struct Rank : Rank<N - 1> {};
template <>
struct Rank<0> {};
[[maybe_unused]] static std::tuple<> CollectField(Rank<0>);
struct Helper {
template <class T>
static auto getFieldInfo() -> decltype(T::CollectField(Rank<>{}));
template <class T>
using FieldInfoList = decltype(getFieldInfo<T>());
template <class T>
static constexpr size_t Size = std::tuple_size_v<FieldInfoList<T>>;
template <class T, size_t I>
using FieldInfo = std::tuple_element_t<I, FieldInfoList<T>>;
};
#define REFL_NOW decltype(CollectField(::reflection::Rank<>{}))
#define REFL_ADD(info) \
static ::reflection::Append<REFL_NOW, decltype(info)>::type CollectField( \
::reflection::Rank<std::tuple_size_v<REFL_NOW> + 1>)
} // namespace reflection
namespace thief {
template <class T>
struct Bridge {
#if defined(__GNUC__) && !defined(__clang__)
// Silence unnecessary warning
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnon-template-friend"
#endif
friend auto ADL(Bridge<T>);
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
};
template <class T, class U>
struct StealType {
friend auto ADL(Bridge<T>) { return std::type_identity<U>{}; }
};
template <class Tag, class Store>
using steal = decltype(StealType<Tag, Store>{});
template <class Tag>
using retrieve = typename decltype(ADL(Bridge<Tag>{}))::type;
} // namespace thief
template <size_t N>
struct NameWrapper {
constexpr NameWrapper(const char (&str)[N]) { std::copy_n(str, N, string); }
constexpr operator std::string_view() const { return {string, N - 1}; }
char string[N];
};
template <NameWrapper Name, auto Getter>
struct FieldInfo {
static constexpr std::string_view name = Name;
static constexpr auto getter = Getter;
};
#define ADD_FIELD(NAME, DEFAULT) \
protected: \
friend struct ::reflection::Helper; \
struct T##NAME : std::type_identity<decltype(DEFAULT)> {}; \
\
public: \
T##NAME::type NAME = DEFAULT; \
\
private: \
constexpr auto T##NAME() \
->::thief::steal<struct T##NAME, std::decay_t<decltype(*this)>>; \
REFL_ADD((FieldInfo<#NAME, &thief::retrieve<struct T##NAME>::NAME>{}))
struct A {
ADD_FIELD(a, int{});
ADD_FIELD(b, short{});
ADD_FIELD(c, std::string{});
};
static_assert(reflection::Helper::Size<A> == 3);
static_assert(reflection::Helper::FieldInfo<A, 0>::name == "a");
static_assert(reflection::Helper::FieldInfo<A, 1>::name == "b");
static_assert(reflection::Helper::FieldInfo<A, 2>::name == "c");
int main() {
A a;
a.a = 10;
a.b = 20;
a.c = "hello";
std::apply(
[&](auto&&... type) {
((std::cout << "name: " << type.name << ", value: " << a.*type.getter
<< std::endl),
...);
},
::reflection::Helper::FieldInfoList<A>{});
return 0;
}
/*
* name: a, value: 10
* name: b, value: 20
* name: c, value: hello
*/
有了静态反射,序列化就变成了一项体力活了。这里参考了 eyalz800/zpp_bits 项目中的二进制序列化方式,同时使用 marzer/tomlplusplus 也实现了一套序列化到 TOML 类型的逻辑。