先说结论,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规则无需再手写。
现在把所有代码组合起来:
[code]template struct Functor: Ts...{ using Ts: perator()...;};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 |