C++ lambda的重载
先说结论,lambda是不能重载的(至少到c++23依旧如此,以后会怎么样没人知道)。而且即使代码完全一样的两个lambda也会有完全不同的类型。
但虽然不能直接实现lambda重载,我们有办法去模拟。
在介绍怎么模拟之前,我们先看看c++里的functor是怎么重载的。
首先类的函数调用运算符是可以重载的,可以这样写:
struct Functor {
bool operator()(int i) const
{
return i % 2 == 0;
}
bool operator()(const std::string &s) const
{
return s.size() % 2 == 0;
}
};
在此基础上,c++11还引入了using的新用法,可以把基类的方法提升至子类中,子类无需手动重写就可直接使用这些基类的方法:
struct IntFunctor {
bool operator()(int i) const
{
return i % 2 == 0;
}
};
struct StrFunctor {
bool operator()(const std::string &s) const
{
return s.size() % 2 == 0;
}
};
struct Functor: IntFunctor, StrFunctor {
// 不需要给出完整的签名,给出名字就可以了
// 如果在基类中这个名字已经有重载,所有重载的方法也会被引入
using IntFunctor::operator();
using StrFunctor::operator();
};
auto f = Functor{};
现在Functor可以直接使用bool operator()(const std::string &s)和bool operator()(int i)了。
现在可以看看怎么模拟lambda重载了:我们知道c++标准要求编译器把lambda转换成类似上面的Functor的东西,因此也能使用上面的办法模拟重载。
但还有两个致命问题:第一是需要写明需要继承的lambda的类型,这个当然除了模板之外是做不到的;第二是继承的基类的数量得明确给出这限制了灵活性,但可以用c++11添加的新特性——变长模板参数来解决。
解决上面两个问题其实很简单,方案如下:
template <typename... Ts>
struct Functor: Ts...
{
using Ts::operator()...;
};
auto f = Functor<StrFunctor, IntFunctor>{};
使用变长模板参数后就可以继承任意多的类了,然后再使用...在类的内部逐个引入基类的函数调用运算符。
这样把继承的对象从普通的类改成lambda就可以模拟重载。但是怎么做呢,前面说了我们没法直接拿到lambda的类型,用decltype的话又会非常啰嗦。
答案是可以依赖c++17的新特性:CTAD。简单得说就是可以提前指定规则,让编译器从构造函数或者符合要求的构造方式里推导需要的类型参数。于是可以这样写:
template <typename... Ts>
Functor(Ts...) -> Functor<Ts...>;
箭头左边的是构造函数,右边的是推导出来的类型。
现在又有疑问了,Functor里不是没定义过任何构造函数吗?是的,正是因为没有定义,使得Functor符合条件成为了“聚合”(aggregate)。“聚合”可以做聚合初始化,形式类似:聚合{基类1初始化,基类2初始化, ...,成员变量1的值,成员变量2的值...}。
作为一种符合要求的初始化方式,也可以使用CTAD,但形式上会用圆括号包起来导致看着像构造函数。另外对于聚合,c++20会自动生成和上面一样的CTAD规则无需再手写。
现在把所有代码组合起来:
template <typename... Ts>
struct Functor: Ts...
{
using Ts::operator()...;
};
int main()
{
const double num = 2.0;
auto f = Functor{
[](int i) { return i+1; },
[&num](double d) { return d+num; },
[s = std::string{}](const std::string &data) mutable {
s = data + s;
return s;
}
};
std::cout << f(1) << '\n';
std::cout << f(1.0) << '\n';
std::cout << f("apocelipes!") << '\n';
std::cout << f("Hello, ") << '\n';
// Output:
// 2
// 3
// apocelipes!
// Hello, apocelipes!
}
有没有替代方案?c++17之后是有的,可以利用if constexpr或者if consteval对类型分别进行处理,编译器编译时会忽略其他分支,实际上这不是重载,但实现了类似的效果:
int main()
{
auto f = []template <typename T>(T t) {
if constexpr (std::is_same_v<T, int>) {
return t + 1;
}
else if constexpr (std::is_same_v<T, std::string>) {
return "Hello, " + t;
}
else {
return t;
}
};
std::cout << f(1) << '\n';
std::cout << f("apocelipes") << '\n';
std::cout << f(1.2) << '\n';
// Output:
// 2
// Hello, apocelipes
// 1.2
}
要注意的是这里的f本身并不是模板,f的operator()才是。这个方案除了啰嗦之外和上面靠继承的方案没有太大区别。
lambda重载有啥用呢?目前一大用处是可以简化std::visit的使用:
std::variant<int, long, double, std::string> v;
// 对v一顿操作
std::visit(Functor{
[](int arg) { std::cout << arg << ' '; },
[](long arg) { std::cout << arg << ' '; },
[](double arg) { std::cout << std::fixed << arg << ' '; },
[](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
}, v);
这个场景中需要一个callable对象,同时需要它的调用运算符有对应类型的重载,在这里不能直接用模板,所以我们的模拟lambda重载派上了用场。
如果要我推荐的话,我会选择继承的方式实现lambda重载,虽然一般不推荐使用多继承,但这里的多继承不会引发问题,而且可读性能获得很大提升,优势很明显,所以首选这种方案。
C++ lambda的重载的更多相关文章
- 【c++ Prime 学习笔记】第14章 重载运算与类型转换
14.1 基本概念 重载的运算符是特殊的函数:名字由关键字operator后接要定义的算符共同组成,也有返回类型.参数列表.函数体. 重载运算符函数的参数量与该算符作用的运算对象数量一样多 除重载调用 ...
- [C++ Primer] : 第10章: 泛型算法
概述 泛型算法: 称它们为"算法", 是因为它们实现了一些经典算法的公共接口, 如搜索和排序; 称它们是"泛型的", 是因为它们可以用于不同类型的元素和多种容器 ...
- lambda表达式与方法重载问题
笔者之前在学习Java8新特性的时候,最吸引我的就是lambda表达式,它无疑为Java函数编程提供了强有力的支持.lambda表达式的使用方法很简单,下面给出最简单的用法. // Interface ...
- 背后的故事之 - 快乐的Lambda表达式(一)
快乐的Lambda表达式(二) 自从Lambda随.NET Framework3.5出现在.NET开发者眼前以来,它已经给我们带来了太多的欣喜.它优雅,对开发者更友好,能提高开发效率,天啊!它还有可能 ...
- Lambda
Lambda Lambda 表达式是一种可用于创建委托或表达式目录树类型的匿名函数. 通过使用 lambda 表达式,可作为参数传递或作为函数调用值返回的本地函数. Lambda 表达式对于编写 LI ...
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...
- .NET中那些所谓的新语法之三:系统预定义委托与Lambda表达式
开篇:在上一篇中,我们了解了匿名类.匿名方法与扩展方法等所谓的新语法,这一篇我们继续征程,看看系统预定义委托(Action/Func/Predicate)和超爱的Lambda表达式.为了方便码农们,. ...
- 匿名方法与Lambda表达式
1.匿名方法 在学习委托时,我们知道委托实例至少要绑定一个方法才能使用,而调用委托实际上是调用了它所关联地方法.一般来说,需要定义一个与委托签名相符的方法,并使之与委托变量关联.如以下代码: Acti ...
- VS2012 Unit Test(Void, Action, Func) —— 对无返回值、使用Action或Func作为参数、多重载的方法进行单元测试
[提示] 1. 阅读文本前希望您具备如下知识:了解单元测试,了解Dynamic,熟悉泛型(协变与逆变)和Lambda,熟悉.NET Framework提供的 Action与Func委托.2.如果您对单 ...
- 19、lambda表达式树
一.定义: 表达式树又称为表达式目录树,以数据形式表示语言级代码.所有的数据都存储在树结构中,每个结点表示一个表达式(Expression). 二.要点: –Lambda表达式的参数类型可以忽略,因为 ...
随机推荐
- MicroNet: 低秩近似分解卷积以及超强激活函数,碾压MobileNet | 2020新文分析
论文提出应对极低计算量场景的轻量级网络MicroNet,包含两个核心思路Micro-Factorized convolution和Dynamic Shift-Max,Micro-Factorized ...
- KingbaseES 用户密码认证及加密算法
kingbaseES用户的口令被存储在sys_authid系统表中. 口令可以用SQL命令create user 和alter user 管理,例如 :create/alter user u1 wit ...
- ubuntu 联网
使用NAT
- 使用Python的turtle模块绘制美丽的樱花树
引言 Python的turtle模块是一个直观的图形化编程工具,让用户通过控制海龟在屏幕上的移动来绘制各种形状和图案.turtle模块的独特之处在于其简洁易懂的操作方式以及与用户的互动性.用户可以轻松 ...
- C 语言入门:如何编写 Hello World
C 语言简介 C 语言是由 Dennis Ritchie 于 1972 年在贝尔实验室创建的一种通用编程语言.尽管年代久远,它仍然是一款非常流行的语言.它之所以受欢迎的主要原因是它是计算机科学领域的基 ...
- C++ 中的可移植性和跨平台开发
在当今软件开发行业中,跨平台开发已经成为了一种非常流行的方式.C++作为一门强大的编程语言,也被广泛应用于跨平台开发中.然而,由于不同操作系统的差异和限制,C++在不同的平台上的表现可能会有所不同.为 ...
- 信息泄露漏洞的JS整改方案
引言 ️ 日常工作中,我们经常会面临线上环境被第三方安全厂商扫描出JS信息泄露漏洞的情况,这给我们的系统安全带来了潜在威胁.但幸运的是,对于这类漏洞的整改并不复杂.本文将介绍几种可行的整改方法,以及其 ...
- 【FAQ】接入HMS Core广告服务中的常见问题总结和解决方法
HMS Core广告服务(Ads Kit)为开发者提供流量变现服务和广告标识服务,依托华为终端能力,整合资源,帮助开发者获取高质量的广告内容.同时提供转化跟踪参数服务,支持三方监测平台.广告主进行转化 ...
- MogDB/openGauss 坏块测试-对启动的影响-测试笔记1
MogDB/openGauss 坏块测试-对启动的影响-测试笔记 1 在 UPDATE 操作提交后,脏块落盘前 kill 掉 mogdb 数据库,然后对 UPDATE 修改的坏进行以下破坏操作,仍然能 ...
- 进阶 stack smashing--canary 报错利用 && environ泄露栈地址
进阶 stack smashing--canary 报错利用 && environ泄露栈地址 这部分是对进阶stack smashing的使用,以及对 environ的认识,我们可以看 ...