最近发现泛型编程有了另一利器——泛型Lambda,比想象当中要强大许多,威力不小,和大家分享一下。
本篇内容需要对泛型编程有所了解,若是读过之前更新的相关文章,食用更佳。
开始之前,先来简单回顾一下泛型编程的内容。
泛型编程的目的是将「数据和方法」进行分离,将数据高度抽象,于是可以表示同类问题的「最小通解」。
C++中,通过模板来实现泛型编程,模板又分为变量模板、函数模板和类模板。
这些模板始终围绕着「数据和方法」。变量模板属于对数据类型的抽象,函数模板属于对方法的抽象,而类模板,则二者兼有,因为类本身的目的就是将数据和方法进行结合。
因此为什么说函数模板处理的是数值,而类模板处理的是类型呢?就是由于函数只具有方法,而在C++中方法是不支持偏特化的,所以它无法处理类型。
到了C++14,Lambda也迎来了泛化能力,称为Generic Lambda。不过此时的泛化能力只是由auto带来的,威力略弱。
随后又经过多年的发展,Lambda的能力越来越强。C++20加入了Template Lambda,这让Lambda也可以指定模板参数,使得Lambda的泛化能力更加完善。
至此,C++的泛型编程多了一个新的主角——泛型Lambda。
为何泛型Lambda值得单独拿出来说呢?
一是因其特殊性,在一些情境使用它来封装变化,会让事情简单许多;二是由其新颖性,它的许多特性和用处尚处探索期,值得讨论。
首先来说其特殊性。
Lambda函数其实就是一个匿名的函数对象,它实际上也是一个”类”。不同的是,它唯一的方法就是operator(),也就是Lambda体,而数据则是[]中捕获的参数,这些参数就是”类”中定义的成员变量。
因此,Lambda函数既具有函数的部分特征,又具有类的部分特征。
也因如此,事情变得有趣起来。
Lambda具有的函数部分特征,让它具备了函数模板的能力;类部分特征,让它具备了类模板的继承能力。
此外,由于Lambda的类型是一个closure type(闭包类型),所以它还可以定义在函数内部,也可以当作回调函数使用。
如此这些,再加上泛型,使得泛型Lambda极具威力。
继而来看其新颖性。
当下大多数C++开发者对于Lambda的使用,还只是停留在函数部分,相当于只发挥了Lambda的基本能力。
实际上,Lambda的能力要比想象之中强大许多,在基本能力之上,还有些令人兴奋的能力。
这也是值得探索的地方。
不论写库或框架,都是在提炼「不变」的逻辑,将「变化」的逻辑交给用户配置。
可以是预留接口,让用户覆写接口;也可以是采用回调,让用户提供处理逻辑;抑或是提供配置文件,让用户填写变化的信息,再通过配置文件自动生成相应处理逻辑。
应对「变化」的方式很多,对于一些逻辑不甚复杂的变化,完全可以借助Lambda来实现。
Lambda天生可以在函数内部构建,自带一个operator (),这就相当于一个表示变化的接口,也就是用户可以手动配置的地方。
有了表示变化的地方,你再将不变的逻辑封装到一个类中,让该类继承自此Lambda。于是,你便可以在不变之中使用变化的逻辑。
既然泛型Lambda具备函数模板的特性,那么它是否也可以重载呢?
回答是no。前文提到,Lambda是函数对象,它只有唯一的一个方法operator(),也就是Lambda体,Lambda体只有一个,你又如何能写多个呢?
但是,可以提供多个Lambda,也就是造就多个函数对象,让它们参数不同,再借助某种技巧,便可以从「视觉层面」实现Lambda重载。
说是「视觉层面」,意思是说它本质上不是函数意义上的那种重载,只是使用起来像是函数重载一样。
这个技巧就是overload pattern。
其实早在[C++ DP.13-2 泛化实现Cyclic Visitor与强大的C++17 std::visit] 这篇文章中,就已经提到并使用了这个技巧。
这里再次拿出一节来介绍它,是因为我发现它比想象之中更加强大,可以说是泛型Lambda编程的一个核心技术。
它的实作很简单,只有两行代码:
1template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
2template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
各位都知道,C++中代码越少往往并不意味着它有多简单,而是说明其「信息密度」较大。
此处,第一行首先使用了可变参数模板,使得overloaded可以继承自多个Lambda。其次使用了Using-declaration,以防止重载之时产生歧义。
第二行则使用了C++17的CTAD(Class Template Argument Deduction),以推导出overloaded的类型。有何必要呢?这是因为你无法创建一个overloaded类型的对象,因为Lambda的类型不可知,你无法填写模板参数类型。借由CTAD,便可以为overloaded添加一个用户自定义的类型推导指引,这样编译器才能够推导出其类型。
现在,就可以使用「视觉层面」的Lambda重载了:
1const auto func = overloaded {
2 [](const int& n) { std::cout << "int:" << n << '\n'; },\
3 [](const std::string& s) { std::cout << "string:" << s << '\n'; }
4};
5
6func(2);
7func("im the lambda with parameter std::string");
这里又使用了「聚合初始化」,通过它可以直接调用基类中Lambda的构造器,从而避免为overloaded显式编写构造函数向基类传递参数。
总而言之,通过Lambda重载,便可以将许多相似的「变化逻辑」聚到一起,再以不同的参数访问这些不同的逻辑,从而以一种崭新的形式封装变化。
这一节需要你对[C++ DP.08 Factory Method] 这篇文章有些印象。
通过泛型Lambda,我们拥有了一种新的实现对象工厂的策略,简单而威力巨大。
代码如下:
1template<class... Ts> struct Fruit : Ts... { using Ts::operator()...; };
2template<class... Ts> Fruit(Ts...) -> Fruit<Ts...>;
是的,就是使用了Lambda重载来实现Fruit。
然后再通过以下形式定义对象工厂:
1struct Apple { void print() { std::cout << "apple print\n"; } };
2struct Pineapplce {void print() { std::cout << "pineapple print\n"; } };
3
4// 定义对象工厂
5static constexpr auto FruitFactory = Fruit {
6 []<typename T>(const T& apple) { return new T; }
7};
8
9// 从工厂创建产品
10auto apple = FruitFactory(Apple{});
11apple->print();
此处第6行代码便使用了C++20的Template Lambda,由此我们可以创建任意类型的对象。
如此少的代码,实现的对象工厂可并不弱,而且这种实现方法更加轻便,除了无法动态产生,已经相当不错了。
这一节需要你对[C++ DP.09 Abstract Factory] 这篇文章有些印象。
没错,根据泛型Lambda,实现抽象工厂也有了一种新的形式。
并且这种形式使用起来更加轻便,我已经决定使用这种方式替换okdp中的实现。
我们可以通过Lambda重载来定义抽象工厂:
1template<class... Ts> struct AbstractAIFactory : Ts... { using Ts::operator()...; };
2template<class... Ts> AbstractAIFactory(Ts...) -> AbstractAIFactory<Ts...>;
具体工厂的定义则更具有技巧性,实现如下:
1template<class T, class U>
2concept IsAbstractAI = std::same_as<T, U>;
3
4template<class T>
5static constexpr auto AIFactory = AbstractAIFactory {
6 []() requires IsAbstractAI<T, Lux> { return new LuxEasy; },
7 []() requires IsAbstractAI<T, Ziggs> { return new ZiggsEasy; },
8 []() requires IsAbstractAI<T, Teemo> { return new TeemoEasy; }
9};
10
11auto lux = AIFactory<Ziggs>();
12lux->print();
你是否意识到了这种实现形式的强大之处?
这里用到的技术就更加多了,除了前面介绍的「聚合初始化」,还使用到了C++20的Concepts,这点我们已经写过文章了,相信大家不会太陌生。
此外,这里还用到了「变量模板」,想想前面几节的内容,提到过范型Lambda虽然具有函数模板和类模板的部分特征,但它的「数据」部分只能通过捕获参数。因此其实无法真正像类那样使用,而抽象工厂的抽象类又无法实例化,所以我们也无法像对象工厂那样使用。
于是,为了为它添上「类型的能力」,这里借助了变量模板。正因如此,你才能像类一样使用AIFactory。
不过事情尚未结束,此时「抽象工厂」就是AbstractAIFactory,通过Lambda重载完成的不错。「具体工厂」属于变化的部分,就相当于Lambda体,也就是这里为每个类型实现的Lambda函数。
问题在哪呢?巨大的重复!
消除这种类型的重复比较好的方法是借助「泛型宏」,这点在对象工厂那篇文章中介绍并使用过。
泛型编程是理念,模板是手段,宏同样是一种手段,需要根据具体情形具体分析,从而合理地进行选择。
不过此处的情形有些复杂,泛型宏的确可以很好的完成任务,但是工作比较复杂,已经涉及到泛型宏深入层次的技术了。
因此由于本篇主题不是泛型宏,篇幅有限,此处只展示下代码,不做进一步解释,大家可以自己看看。
代码如下:
#define _GET_OVERRIDE(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
#define _CONCRETE_AI_FACTORY_BODY_0(LAM, AIType, LEvel)
#define _CONCRETE_AI_FACTORY_BODY_1(LAM, AIType, Level, AIName) LAM(AIType, Level, AIName)
#define _CONCRETE_AI_FACTORY_BODY_2(LAM, AIType, Level, AIName, ...) LAM(AIType, Level, AIName) _CONCRETE_AI_FACTORY_BODY_1(LAM, AIType, Level, __VA_ARGS__)
#define _CONCRETE_AI_FACTORY_BODY_3(LAM, AIType, Level, AIName, ...) LAM(AIType, Level, AIName) _CONCRETE_AI_FACTORY_BODY_2(LAM, AIType, Level, __VA_ARGS__)
#define _CONCRETE_AI_FACTORY_BODY_4(LAM, AIType, Level, AIName, ...) LAM(AIType, Level, AIName) _CONCRETE_AI_FACTORY_BODY_3(LAM, AIType, Level, __VA_ARGS__)
#define _CONCRETE_AI_FACTORY_BODY_5(LAM, AIType, Level, AIName, ...) LAM(AIType, Level, AIName) _CONCRETE_AI_FACTORY_BODY_4(LAM, AIType, Level, __VA_ARGS__)
#define _GENERATE_AI_LAMBDA(AIType, Level, AIName) []() requires IsAbstractAI<AIType, AIName> { return new AIName##Level; },
#define CONCRETE_AI_FACTORY(AIType, Level, ...) \
_GET_OVERRIDE("ignored", ##__VA_ARGS__, \
_CONCRETE_AI_FACTORY_BODY_5, _CONCRETE_AI_FACTORY_BODY_4, \
_CONCRETE_AI_FACTORY_BODY_3, _CONCRETE_AI_FACTORY_BODY_2, \
_CONCRETE_AI_FACTORY_BODY_1, _CONCRETE_AI_FACTORY_BODY_0) \
(_GENERATE_AI_LAMBDA, AIType, Level, ##__VA_ARGS__)
泛型宏的实现复杂是针对开发者来说的,对于使用者来说却是极为简单。
现在你可以非常简单地使用宏实现的「具体工厂」来替换前面的写法:
template<class T>
static constexpr auto AIFactory = AbstractAIFactory {
// []() requires IsAbstractAI<T, Lux> { return new LuxEasy; },
// []() requires IsAbstractAI<T, Ziggs> { return new ZiggsEasy; },
// []() requires IsAbstractAI<T, Teemo> { return new TeemoEasy; }
CONCRETE_AI_FACTORY(T, Easy, Lux, Ziggs, Teemo)
};
不论你有多少产品,都可以由该具体工厂轻松实现,是不是很强大!
本篇内容应该是我写的涉及内容最广的文章之一了,光之前写过的文章就引用了多篇。
所以对大家的要求也会有点高,可以多看几遍。
另外这篇的内容其实很“新”,首先组合使用了许多C++20特性,其次涉及了大量泛型编程技术,文章介绍的泛型Lambda技术现在还不是很流行,使用场景也是慢慢摸索出来的,比较成熟的想法都写在了本文之中。
但是它的用处我感觉还有很多,还在研究中,后续再和大家分享。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/fZlSPdbyGofuT6-Wy_vJsg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。