张家口购物网站开发设计,简述网页的制作过程,互联网公司包括,wordpress改为在线考试原文
C17基于结构绑定的编译期反射
事实上不需要宏的编译期反射在C17中已用得很多了,比如struct_pack的编译期反射就不需要宏,因为C17结构绑定可直接得到一个聚集类的成员的引用.
struct person {int id;std::string name;int age;
};
int main() {person p{1, tom17基于结构绑定的编译期反射
事实上不需要宏的编译期反射在C17中已用得很多了,比如struct_pack的编译期反射就不需要宏,因为C17结构绑定可直接得到一个聚集类的成员的引用.
struct person {int id;std::string name;int age;
};
int main() {person p{1, tom, 20};auto [id, name, age] p;std::cout name \n;
}没有宏也没有侵入式,一切都很完美,但是有两个比较大的问题:
问题一 结构绑定方式无法取字段名,这是一个主要问题,如果想序化对象到json,xml时,需要字段名时就无法满足需求了. 问题二 除此外还有另外一个问题,如果一个对象有构造器或私有成员时,则它就不是一个聚集类型了,无法再用结构绑定去反射内部的字段了.
基于结构绑定的编译期反射无法解决这两个主要问题,导致用途不大.
yalantinglibs.reflection反射库
yalantinglibs.reflection反射库直面并解决了这两个问题,提供了统一的编译期反射方法,无论对象是否是聚集类型,无论对象是否有私有字段都可用统一的api在编译期反射得到其元信息.
来看看yalantinglibs.reflection如何反射一个聚集类型的:
struct simple {int color;int id;std::string str;int age;
};
using namespace ylt::reflection;
int main() {simple p{.color 2, .id 10, .str hello reflection, .age 6};//取对象字段个数static_assert(members_count_vsimple 4);//取对象所有字段名 constexpr auto arr member_namessimple; //std::arraystd::string_view, N //根据字段索引取字段值 CHECK(std::get3 6); //get age CHECK(std::get2 hello reflection); //get str //根据字段名取字段值auto age2 getage_ylts(p);CHECK(age2 6);//遍历对象,得到字段值和字段名for_each(p, [](auto field_value, auto field_name) {std::cout field_value , field_name \n;});
}yalantinglibs的编译期反射相比前结构绑定方式的反射更进一步了,不仅是无宏非侵入式,还能得到字段名,这样就把第一个问题解决掉了,可用在数格和xml等需要字段名的场景下了.
yalantinglibs.reflection是如何完成非侵入式取得聚集对象的字段名的呢?因为reflectcpp这道光!
该库的作者发现了一个新方法可在C20高版本的编译器中非侵入式的取得聚集对象的字段名.能发现该方法我只能说你真是个天才!
该方法说起来也不算复杂,分两步实现: 第一步:在编译期取得对象字段值的指针; 第二步:在编译期第一步得到的指针解析出字段名; 是不是很简单,接着看看具体是如何实现的吧.
在编译期取得对象字段值的指针
reflectcpp在实现这一步时做得比较复杂,yalantinglibs大幅简化了这一步的实现.
template class T, std::size_t n
struct object_tuple_view_helper {static constexpr auto tuple_view(){}
};
template class T
struct object_tuple_view_helperT, 0 {static constexpr auto tuple_view() { return std::tie(); }
};
template class T
struct object_tuple_view_helperT, 4 {static constexpr auto tuple_view() {auto [a, b, c, d] get_fake_objectremove_cvref_tT();auto ref_tup std::tie(a, b, c, d);auto get_ptrs [](auto... _refs) {return std::make_tuple(_refs...);};return std::apply(get_ptrs, ref_tup);}
};偏特化模板类object_tuple_view_helper,在tuple_view函数中,先结构绑定得到字段值的引用,然后按指针转换它,并放到元组中,来返回给用户.
这里偏特化的关键在于n,它表示聚集对象字段的个数,该字段个数是可在编译期取的.为了避免针对不同字段个数的聚集类型写重复的偏特化的代码,可用脚本生成这些代码.
#define RFL_INTERNAL_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS( \n, ...) \template class T \struct object_tuple_view_helperT, n { \static constexpr auto tuple_view() { \auto [__VA_ARGS__] get_fake_objectremove_cvref_tT(); \auto ref_tup std::tie(__VA_ARGS__); \auto get_ptrs [](auto... _refs) { \return std::make_tuple(_refs...); \}; \return std::apply(get_ptrs, ref_tup); \} \}
/*The following boilerplate code was generated using a Python script:
macro
RFL_INTERNAL_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS
with open(generated_code4.cpp, w, encodingutf-8) as codefile:codefile.write(\n.join([f{macro}({i}, {, .join([ff{j} for j in range(i)])});for i in range(1, 256)]))
*/
RFL_INTERNAL_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS(1, f0);
RFL_INTERNAL_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS(2, f0, f1);
RFL_INTERNAL_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS(3, f0, f1, f2);
RFL_INTERNAL_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS( 4, f0, f1, f2, f3);
RFL_INTERNAL_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS( 5, f0, f1, f2, f3, f4);
RFL_INTERNAL_OBJECT_IF_YOU_SEE_AN_ERROR_REFER_TO_DOCUMENTATION_ON_C_ARRAYS( 6, f0, f1, f2, f3, f4, f5);
...生成偏特化代码后,就可简单取得聚集对象字段的指针.
template class T
inline constexpr auto struct_to_tuple() {return object_tuple_view_helperT, members_count_vT::tuple_view();
}调用struct_to_tuple就能在编译期取得T所有字段的指针了,其中编译期取T的字段个数的方法members_count_v就是来自于struct_pack中的方法,前面在介绍struct_pack的文章里已详细讲过,就不再赘述了.
根据字段指针取字段名
有了编译期得到的字段指针后就很容易取其字段名了:
template auto ptr
inline constexpr std::string_view get_member_name() {
#if defined(_MSC_VER)constexpr std::string_view func_name __FUNCSIG__;
#elseconstexpr std::string_view func_name __PRETTY_FUNCTION__;
#endif
#if defined(__clang__)auto split func_name.substr(0, func_name.size() - 2);return split.substr(split.find_last_of(:.) 1);
#elif defined(__GNUC__)auto split func_name.substr(0, func_name.rfind()}));return split.substr(split.find_last_of(:) 1);
#elif defined(_MSC_VER)auto split func_name.substr(0, func_name.rfind(}));return split.substr(split.rfind(-) 2);
#elsestatic_assert(false,You are using an unsupported compiler. Please use GCC, Clang or MSVC or switch to the rfl::Fieldsyntax.);
#endif
}templateauto ptr是C17的特性,可用动来声明一个非类型模板参数,来避免写具体类型.
有了该编译期的针后,剩下的就是根据编译产生的符号去截取需要的部分串了,注意每个平台生成的符号有差异,需要宏来区分各个平台的截取方式.
为什么用指针可以取字段名?C17或以下的编译器是不是也可这样来取呢? 第一个问题的答案是:reflectcpp作者发现的该方法,很黑客,但是工作! 第二个问题的答案是:不可以,该方法只在支持C20的gcc11,clang13,msvc2022以上编译器中才有效!
所以该非侵入式取字段名的方法也是有约束的,不适合低版本的编译器.
完整的取字段列表的实现:
template class T
struct Wrapper {using Type T;T v;
};
template class T
Wrapper(T) - WrapperT;
//针对clang.
template class T
inline constexpr auto wrap(const T arg) noexcept {return Wrapper{arg};
}
template typename T
inline constexpr std::arraystd::string_view, members_count_vT
get_member_names() {constexpr auto tp struct_to_tupleT();std::arraystd::string_view, Count arr;[]size_t... Is(std::index_sequenceIs...) mutable {((arr[Is] get_member_namewrap(std::getIs(tp))()), ...);}(std::make_index_sequenceCount{});return arr;
}至此,两步完成,可用get_member_names函数非侵入式的取得聚集对象的字段名列表了.
如何处理非聚集类型?
在高版本的编译器中无宏非侵入式得到聚集对象字段名列表固然很好,但是非聚集类型要如何处理呢?如果编译器版本不够,只有C17又该怎么办?
yalantinglibs.reflection的未来远不止你想象的,想能统一整个编译期反射的内容,无论对象是聚集还是非聚集,无论对象是否含有私有字段都提供统一的反射接口!
比如像这样一个对象:
class private_struct {int a;int b;public:private_struct(int x, int y) : a(x), b(y) {}
};private_struct st(2, 4);ylt::reflection::refl_visit_members(st, [](auto... args) {((std::cout args ), ...);std::cout \n;});private_struct是一个含私有字段的非聚集类型,但是ylt::reflection也能反射它.但是这里还漏掉了一个宏,是,还是需要宏,在编译期反射进入到标准库前,对非聚集类型仍需要的.
class private_struct {int a;int b;public:private_struct(int x, int y) : a(x), b(y) {}
};
YLT_REFL_PRIVATE(private_struct, a, b);对没有私字段的非聚集类型来说,也适合C17,可这样定义宏:
struct dummy_t {int id;std::string name;int age;YLT_REFL(dummy_t, id, name, age);
};
struct dummy_t2 {int id;std::string name;int age;
};
YLT_REFL(dummy_t2, id, name, age);总结
高版本的编译器中可完全不使用宏,低版本或非聚集类型宏来实现编译器反射,无论是聚集还是非聚集都是使用同一套反射接口,这样可覆盖所有要用编译期反射的场景,这就是yalantinglibs.reflection提供的能力!
后面struct_pack,struct_pb,struct_json,struct_xml,struct_yaml都会使用yalantinglibs.reflection提供的统一的编译期反射接口来实现序化和反序化.