首页 > 编程语言 >C++ lambda的重载

C++ lambda的重载

时间:2024-05-05 11:11:36浏览次数:16  
标签:std return Functor operator 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重载,虽然一般不推荐使用多继承,但这里的多继承不会引发问题,而且可读性能获得很大提升,优势很明显,所以首选这种方案。

标签:std,return,Functor,operator,C++,重载,lambda
From: https://www.cnblogs.com/apocelipes/p/18173092

相关文章

  • C++-研讨会(全)
    C++研讨会(全)原文:annas-archive.org/md5/5ba4b421a6ba3d7c3a23406bab386ec0译者:飞龙协议:CCBY-NC-SA4.0前言关于本书C#是一种强大而多才多艺的面向对象编程(OOP)语言,可以打开各种职业道路。但是,与任何编程语言一样,学习C#可能是具有挑战性的。由于有各种不同的资源可用,很难......
  • C++-专家编程(全)
    C++专家编程(全)原文:annas-archive.org/md5/57ea316395e58ce0beb229274ec493fc译者:飞龙协议:CCBY-NC-SA4.0前言学习路径和技术简介。这个学习路径适合谁这个学习路径适合想要提升并学习如何在最新版本的Java中构建健壮应用程序的Java开发人员。这个学习路径涵盖了什......
  • C++-游戏动画编程实用指南(全)
    C++游戏动画编程实用指南(全)原文:annas-archive.org/md5/1ec3311f50b2e1eb4c8d2a6c29a60a6b译者:飞龙协议:CCBY-NC-SA4.0前言现代游戏动画有点像黑魔法。没有太多资源详细介绍如何构建基于轨道驱动的动画系统,或者高级主题,比如双四元数蒙皮。这本书的目标就是填补这个空白。......
  • 【转载】Godot-GDExtension C++ 环境搭建 (Docker+MinGW/跨平台)
    本文原链接见 Godot-GDExtensionC++环境搭建(Docker+MinGW/跨平台)|Convexwf'sKirakiraBlog。Godot在4.X之后推出了GDExtension,通过第三方绑定扩展功能,目前官方支持的语言只有C++。通过使用GDExtensionC++编写扩展插件,可以作为库文件在Godot中交互使用。GDExten......
  • UE4 C++ 杂
    TMap中的Find和FindRef在对蓝图节点进行C++重写时,发现UE对于TMap的Find有很多方式。首先是基础的Find,其就是返回对象类型的指针,如果不存在于TMap中其会返回nullptr接下来是FindChecked,其返回的是对象类型的引用,并且会在内部检测指针是否为空,如果没有会触发断言FindRef其......
  • c++继承两个类怎么实现
    在C++中,继承两个类可以通过多重继承来实现。多重继承允许一个派生类从多个基类继承属性和方法。以下是一个继承两个类的示例:#include<iostream>//第一个基类classBase1{public:voidmethod1(){std::cout<<"Base1method1"<<std::endl;}};//......
  • dotnet的Lambda表达式 委托泛型(2) Action Func
    //总结://泛型:把类,方法,属性,字段做到了通用化//反射:操作dll文件的一个帮助类库//特性:就是一个特殊的类自定义标记属性特性他就是AOP的另一种实现方式验证属性//委托:就是多播委托,可以保存一个或者多个方法的信息。可以用来传递方法(把方法当作参数传递)。主要用来实现代码的解......
  • 运算符重载
    运算符重载基本规则可以重载的运算符:不可重载的运算符://返回类型operator后面加运算符(参数列表)//eg.Integeroperator+(Integerl,Integerr);classInteger{public:Integer(intn=0):i(n){}constIntegeroperator+(constInteger&v){ //在类中......
  • 有关c++STL容器及头文件
    万能头打遍天下无敌手(除了vjudge)#include<bits/stdc++.h>通用函数(在以下STL容器中均适用)size返回容器的实际长度(元素个数)empty返回一个bool型,表示容器是非为空vector声明:vector<int>头文件:Here#include<vector>一些函数:clear清空迭代器与b......
  • 提高安全性,优雅实现拷贝与交换:C++中的Copy-and-Swap惯用法
     概述:拷贝并交换(Copy-and-Swap)是C++编程中的惯用法,用于实现赋值操作符和确保异常安全的拷贝构造函数。其核心思想是通过拷贝构造函数创建临时副本,再通过交换确保操作的异常安全性。这种方法在C++11之前和之后都适用,但在C++11中,移动语义和右值引用的引入使得实现可以更加高效。......