C++标准当前并没有提供反射能力,在yaLanTingLibs中提供了一种反射机制,结合源码对库中使用的编程手法进行简单分析,包括编译期获取结构体成员字段的个数,各个字段的类型;读取结构体字段;
编译期获取结构体字段个数
struct Test {
int32_t v1;
int32_t v2;
int32_t v3;
int32_t v4;
};
Test t {1, 2, 3, 4}; // 完成聚合初始化
Test t {1, 2, 3, 4, 5}; // 编译出错,error: too many initializers for ‘Test’
当初始化个数超过结构体字段个数时则会报错,正是利用这个约束在编译期得到字段个数
template <typename T, typename param, typename = std::void_t<>, typename ...Args>
struct IsConstructable : std::false_type {};
template <typename T, typename param, typename ...Args>
struct IsConstructable<T, param, std::void_t<decltype(T{ Args{}..., param{}})>, Args...> : std::true_type {};
template<typename T, typename ...Args>
constexpr size_t MemberCount()
{
if constexpr (IsConstructable<T, int32_t, void, Args...>::value) {
return MemberCount<T, Args..., int32_t>();
} else {
return sizeof...(Args);
}
}
static_assert(MemberCount<Test>() == 4, "is not equal");
这里结构体中都会int32_t类型,如何支持不同类型?库中引入了AnyType
struct AnyType {
template<typename T>
operator T() {
return T {};
}
};
template <typename T, typename param, typename = std::void_t<>, typename ...Args>
struct IsConstructable : std::false_type {};
template <typename T, typename param, typename ...Args>
struct IsConstructable<T, param, std::void_t<decltype(T{ Args{}..., param{}})>, Args...> : std::true_type {};
template<typename T, typename ...Args>
constexpr size_t MemberCount()
{
if constexpr (IsConstructable<T, AnyType, void, Args...>::value) {
return MemberCount<T, Args..., AnyType>();
} else {
return sizeof...(Args);
}
}
但对于非aggregate类型则不支持,可以通过std::is_aggregate判断;
编译期获取结构体字段类型
struct Test {
int32_t v1;
int32_t v2;
int32_t v3;
std::string v4;
};
Test t{1, 2, 3, "hello world"};
auto &&[v1, v2, v3, v4] = t;
DBG_LOG("%d %d %d %s", v1, v2, v3, v4.c_str());
通过结构化绑定可以获取到结构体成员字段,由此可得到:
template<typename Object, typename Visitor>
constexpr decltype(auto) VisitMember(Object&& obj, Visitor&& vis)
{
constexpr auto count = MemberCount<std::decay_t<decltype(obj)>>();
if constexpr (count == 0) {
return vis();
} else if constexpr (count == 1) {
auto &&[v1] = obj;
return vis(v1);
} else if constexpr (count == 2) {
auto &&[v1, v2] = obj;
return vis(v1, v2);
} else if constexpr (count == 3) {
auto &&[v1, v2, v3] = obj;
return vis(v1, v2, v3);
} else if constexpr (count == 4) {
auto &&[v1, v2, v3, v4] = obj;
return vis(v1, v2, v3, v4);
} else if constexpr (count == 5) {
auto &&[v1, v2, v3, v4, v5] = obj;
return vis(v1, v2, v3, v4, v5);
} else if constexpr (count == 6) {
auto &&[v1, v2, v3, v4, v5, v6] = obj;
return vis(v1, v2, v3, v4, v5, v6);
} else if constexpr (count == 7) {
auto &&[v1, v2, v3, v4, v5, v6, v7] = obj;
return vis(v1, v2, v3, v4, v5, v6, v7);
} else if constexpr (count == 8) {
auto &&[v1, v2, v3, v4, v5, v6, v7, v8] = obj;
return vis(v1, v2, v3, v4, v5, v6, v7, v8);
} else if constexpr (count == 9) {
auto &&[v1, v2, v3, v4, v5, v6, v7, v8, v9] = obj;
return vis(v1, v2, v3, v4, v5, v6, v7, v8, v9);
} else if constexpr (count == 10) {
auto &&[v1, v2, v3, v4, v5, v6, v7, v8, v9, v10] = obj;
return vis(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10);
}
}
此处举例使用10个字段个数进行说明,通过tuple既可以获取字段类型:
using Types = decltype(VisitMember(Test {}, [](auto&&... args) constexpr {
return std::tuple<std::decay_t<decltype(args)>...> {}; })
);
std::cout << "T type:" << type_id_with_cvr<Types>().pretty_name() << std::endl;
输出为:
T type:std::tuple<int, int, int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >
读取结构体字段
VisitMember(Test{1, 2, 3, "hello world"}, [](auto&&... args) {
((std::cout << args << " "), ...);
});
参考资料
【1】https://github.com/alibaba/yalantinglibs