2408C++,C++20的无侵入式反射
原文
C++17基于结构绑定的编译期反射
事实上不需要宏的编译期反射在C++17中已用得很多了,比如struct_pack的编译期反射就不需要宏,因为C++17结构绑定可直接得到一个聚集类的成员的引用.
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_v<simple> == 4);//取对象所有字段名 constexpr auto arr = member_names<simple>; //std::array<std::string_view, N> //根据字段索引取字段值 CHECK(std::get<3> == 6); //get age CHECK(std::get<2> == "hello reflection"); //get str //根据字段名取字段值auto& age2 = get<"age"_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这道光!
该库的作者发现了一个新方法可在C++20高版本的编译器中非侵入式的取得聚集对象的字段名.能发现该方法我只能说你真是个天才!
该方法说起来也不算复杂,分两步实现:
第一步:在编译期取得对象字段值的指针;
第二步:在编译期第一步得到的指针解析出字段名;
是不是很简单,接着看看具体是如何实现的吧.
在编译期取得对象字段值的指针
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_helper<T, 0> {static constexpr auto tuple_view() { return std::tie(); }
};
template <class T>
struct object_tuple_view_helper<T, 4> {static constexpr auto tuple_view() {auto& [a, b, c, d] = get_fake_object<remove_cvref_t<T>>();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_helper<T, n> { \static constexpr auto tuple_view() { \auto& [__VA_ARGS__] = get_fake_object<remove_cvref_t<T>>(); \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", encoding="utf-8") as codefile:codefile.write("\n".join([f"{macro}({i}, {', '.join([f'f{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_helper<T, members_count_v<T>>::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
}
template<auto ptr>是C++17的特性,可用动来声明一个非类型模板参数,来避免写具体类型.
有了该编译期的针后,剩下的就是根据编译产生的符号去截取需要的部分串了,注意每个平台生成的符号有差异,需要宏来区分各个平台的截取方式.
为什么用指针可以取字段名?C++17或以下的编译器是不是也可这样来取呢?
第一个问题的答案是:reflectcpp作者发现的该方法,很黑客,但是工作!
第二个问题的答案是:不可以,该方法只在支持C++20的gcc11,clang13,msvc2022以上编译器中才有效!
所以该非侵入式取字段名的方法也是有约束的,不适合低版本的编译器.
完整的取字段列表的实现:
template <class T>
struct Wrapper {using Type = T;T v;
};
template <class T>
Wrapper(T) -> Wrapper<T>;
//针对clang.
template <class T>
inline constexpr auto wrap(const T& arg) noexcept {return Wrapper{arg};
}
template <typename T>
inline constexpr std::array<std::string_view, members_count_v<T>>
get_member_names() {constexpr auto tp = struct_to_tuple<T>();std::array<std::string_view, Count> arr;[&]<size_t... Is>(std::index_sequence<Is...>) mutable {((arr[Is] = get_member_name<wrap(std::get<Is>(tp))>()), ...);}(std::make_index_sequence<Count>{});return arr;
}
至此,两步完成,可用get_member_names函数非侵入式的取得聚集对象的字段名列表了.
如何处理非聚集类型?
在高版本的编译器中无宏非侵入式得到聚集对象字段名列表固然很好,但是非聚集类型要如何处理呢?如果编译器版本不够,只有C++17又该怎么办?
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);
对没有私字段的非聚集类型来说,也适合C++17,可这样定义宏:
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提供的统一的编译期反射接口来实现序化和反序化.
相关文章:
2408C++,C++20的无侵入式反射
原文 C17基于结构绑定的编译期反射 事实上不需要宏的编译期反射在C17中已用得很多了,比如struct_pack的编译期反射就不需要宏,因为C17结构绑定可直接得到一个聚集类的成员的引用. struct person {int id;std::string name;int age; }; int main() {person p{1, "tom&qu…...
抽象工厂模式(Abstract factory pattern)- python实现
抽象工厂模式的通俗示例 想象一下,你正在经营一家家具店,你需要从不同的供应商那里采购不同的家具系列。有的供应商提供的是现代风格家具,包括现代沙发、现代椅子和现代桌子;而有的供应商提供的是古典风格家具,包括古…...
adb Connection reset by peer的解决方法
本文同步发于:https://www.cnblogs.com/yeshen-org/p/18350232 最近在编译一个老项目,项目中依赖了很多第三方库,用gradle编译要20-30分钟,而且内存开销很大。 公司配的15G内存的电脑,一次编译能用到14G。 编译的时候&…...
111111111
1111111111111111111...
搜维尔科技:Varjo XR-4使用UE5 打造最具沉浸感的混合现实环境
Varjo XR-4使用UE5打造最具沉浸感的混合现实环境 搜维尔科技:Varjo XR-4使用UE5 打造最具沉浸感的混合现实环境...
从分散到集中:TSINGSEE青犀EasyCVR视频汇聚网关在视频整体监控解决方案中的整合作用
边缘计算视频汇聚网关是基于开放式、大融合、全兼容、标准化的设计架构理念,依据《安全防范视频监控联网系统信息传输、交换、控制技术要求》(GB/T28181-2011)标准开发,集流媒体转发、视频编码、视频管理、标准通信协议、网络穿透…...
React学习-jsx语法
jsx语法,浏览器不认识,需要经过babel编译 https://babeljs.io/ 面试题:jsx的作用? 普通回答:可以在js中返回dom,经过babel编译成js认识的代码import { jsx as _jsx, jsxs as _jsxs } from "react/j…...
uniapp多图上传uni.chooseImage上传照片uni.uploadFile
uniapp多图上传uni.chooseImage上传照片uni.uploadFile 代码示例: /**上传照片 多图*/getImage() {uni.chooseImage({count: 9, //默认9sizeType: [original, compressed], //可以指定是原图还是压缩图,默认二者都有sourceType: [album], //从相册选择/…...
鸿蒙(API 12 Beta2版)媒体开发【处理音频焦点事件】
音频打断策略 多音频并发,即多个音频流同时播放。此场景下,如果系统不加管控,会造成多个音频流混音播放,容易让用户感到嘈杂,造成不好的用户体验。为了解决这个问题,系统预设了音频打断策略,对…...
c语言第12天
指针的引入 为函数修改实参提供支持。 为动态内存管理提供支持。 为动态数据结构提供支持。 为内存访问提供另一种途径。 指针概述 内存地址:系统为了内存管理的方便,将内存划分为一个个的内存单元(1个内存单元占1个字 节)&…...
回归预测|一种多输入多输出的粒子群优化支持向量机数据回归预测Matlab程序PSO-MSVR非for循环实现 原理上进行修改多输出
回归预测|一种多输入多输出的粒子群优化支持向量机数据回归预测Matlab程序PSO-MSVR非for循环实现 原理上进行修改多输出 文章目录 前言回归预测|一种多输入多输出的粒子群优化支持向量机数据回归预测Matlab程序PSO-MSVR非for循环实现 原理上进行修改多输出 一、PSO-MSVR模型1. …...
《花100块做个摸鱼小网站! 》第二篇—后端应用搭建和完成第一个爬虫
一、前言 大家好呀,我是summo,前面已经教会大家怎么去阿里云买服务器(链接在这,需要自取:https://developer.aliyun.com/huodong/dashiblogger?userCodemtbtcjr1),以及怎么搭建JDK、Redis、My…...
Mapreduce_csv_averageCSV文件计算平均值
csv文件求某个平均数据 查询每个部门的平均工资,最后输出 数据处理过程 employee_noheader.csv(没做关于首行的处理,运行时请自行删除) EmployeeID,EmployeeName,DepartmentID,Salary 1,ZhangSan,101,5000 2,LiSi,102,6000…...
将UEC++项目转码成UTF-8
方法一 如果文件不多的话,可以手动一个一个进行修改。添加 “高级保存选项” 手动改为UTF-8 方法二 使用editorconfig文件,统一编码问题。通过:“工具” > “选项”>"文本编辑器" > "C/C" > "代码样式…...
深入探索MySQL C API:使用C语言操作MySQL数据库
目录 引言 一. MySQL C API简介 二. MySQL C API核心函数 2.1 初始化和连接 2.2 配置和执行 2.3 处理结果 2.4 清理和关闭 2.5 错误处理 三. MySQL使用过程 四. 实现CRUD操作 4.1 创建数据库并建立表 编辑 4.2 添加数据(Create) 编辑 …...
武汉流星汇聚:亚马逊助力跨境电商扬帆起航,海外影响力显著提升
在全球化浪潮的推动下,跨境电商已成为连接世界市场的重要桥梁。而在这场跨越国界的商业盛宴中,亚马逊作为全球电商的领军者,以其独特的商业模式、庞大的用户基础,为无数企业提供了前所未有的发展机遇。武汉流星汇聚电子商务有限公…...
C语言:设计模式
C语言和设计模式(总结篇) 书籍:《大话设计模式》 2、C语言和设计模式:原型模式(复制自己,生成另外一个实例对象) 17、C语言实现面向对象编程 : 封装、继承、多态 ---- C语言可:封…...
Pandas数据选择的艺术:深入理解loc和iloc
在数据科学领域,Pandas是处理和分析数据的瑞士军刀。掌握Pandas中的数据选择技巧,尤其是loc和iloc的使用,对于提高数据处理效率至关重要。本文将深入探讨loc和iloc的用法,通过丰富的示例,帮助你精确地选取所需的数据&a…...
<数据集>固定视角监控牧场绵羊识别数据集<目标检测>
数据集格式:VOCYOLO格式 图片数量:3615张 标注数量(xml文件个数):3615 标注数量(txt文件个数):3615 标注类别数:1 标注类别名称:[Sheep] 序号类别名称图片数框数1Sheep361529632 使用标注工具&#…...
浙大数据结构慕课课后题(06-图2 Saving James Bond - Easy Version)(拯救007)
题目要求: This time let us consider the situation in the movie "Live and Let Die" in which James Bond, the worlds most famous spy, was captured by a group of drug dealers. He was sent to a small piece of land at the center of a lake fi…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
