泛型lambda表达式

一、Lambda表达式引入泛型

在c++11中引入lambda表达式后,确实是非常好用。但这里有一个问题,lambda表达式无法使用泛型,必须给出指定的类型。这就感官上不符合c++的开发形式,所以在c++14中引入了泛型,也就是可以使用auto做为参数的类型,通过自动推导来确定数据的类型。而在c++20后,更进一步引入模板参数,使得整个泛型推广到了和普通模板编程一致。
这也符合c++语言保持风格一致的指导思想,在这种情况下就不用记各种各样的特殊形式了。

二、具体的应用例程

先看一下在c++14中的泛型应用方式:

#include <iostream>
void TestAutoL(int a,int b)
{
    auto getData = [](auto i, auto j)->auto {
        return i + j;
    };

    int t = getData(a,b);
    std::cout << "getData value is:" << t << std::endl;
}
//早期的版本c++17前,下面可能编译不通过
auto Get(auto x, auto y)
{
    return x + y;
}
int main()
{
    int x = Get(1,3);
    TestAutoL(2,3);
    return 0;
}

再看一下在c++20中的应用方式 :

template<typename T>
int foo1(T t)
{
    return t;
}
template <typename... Args>
int foo(Args ...args)
{
    return (foo1(args) + ...);
}
void TestCpp20(std::string a,std::string b)
{
    auto f1 = []<typename T, typename N>(T t, N n)-> T {
        return t + n;
    };
    auto f2 = []<typename ...T>(T && ...args) {
        return foo(std::forward<T>(args)...);
    };

    std::string s = f1(a,b);
    std::cout << "string is:" << s << std::endl;

    int d = f2(1,2,3,6);
    std::cout << "result is:" << d << std::endl;

}
int main()
{
    std::string s1 = "123";
    std::string s2 = "321";
    std::string ss = s1 + s2;
    TestCpp20(s1,s2);
    return 0;
}

通过上面的对比,是否对lambda的泛型编程有了进一步形象的理解呢。

三、分析

在c++14中,其实是使用了一种取巧的泛型实现方式,通过auto来推导类型。而在同样的c++14中,直接写函数则无法实现类似功能(不过更新的版本则支持了,理论上是一样的),这应该只是一种尝试,这种情况可以认为是一种泛型中的特化,可以把auto当成一种具体的类型,只是这种类型需要简单推导一下。它更类似于下面的代码:

  template<typename T, typename U>
    auto operator()(T t, U u) const {return t + u;}

类似于仿函数的处理,或者说是闭包的一种处理方式。

在c++20中,lambda表达式的应用更加丰富,除了可以引入模板泛型,也可以实现更复杂的功能(比如做为模板参数),看例子:

#pragma once
#include <iostream>
template<typename... Ts> struct Animal : Ts... { using Ts::operator()...; };
template<typename... Ts> Animal(Ts...)->Animal<Ts...>;

struct Pig { void display() { std::cout << "is pig" << std::endl; } };
struct Horse { void display() { std::cout << "is horse"<< std::endl; } };

// 定义
static constexpr auto Factory = Animal{
 []<typename T>(const T& t) { return new T; }
 };
void TestFactory()
{
	auto pig = Factory(Pig{});
	pig->display();
}
int main()
{
    TestFactory();
    return 0;
}

其实在c++的文档中有类似的代码前面也提到过:


template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

上面一行使用了可变参数模板,使得overloaded可以继承自多个Lambda。其次使用了Using-declaration,以防止重载之时产生歧义。

下面一行则使用了C++17的CTAD(Class Template Argument Deduction),以推导出overloaded的类型。
这样做的目的,就是通过CTAD为overloaded添加一个用户自定义的类型推导指引,从而让编译器可以推导lambda的类型,进而可以创建出overloaded类型的对象。
那么可以进一步将抽象:

class Dog
{
public:
	Dog() {  }
	void display() { std::cout << "this is Dog" << std::endl; }
};
class BigDog :public Dog
{
public:
	BigDog() {  }
	void display() { std::cout << "this is BitDog" << std::endl; }
};

class Cow
{
public:
	Cow() {  }
	void display() { std::cout << "this is Cow" << std::endl; }
};
class BigCow :public Cow
{
public:
	BigCow() {  }
	void display() { std::cout << "this is BitCow" << std::endl; }
};

template<typename... Ts> struct AbstractFactory : Ts... { using Ts::operator()...; };
template<typename... Ts> AbstractFactory(Ts...)->AbstractFactory<Ts...>;

template < class T, class U>
concept IsAbstractAnimal = std::same_as<T, U>;

//此处其实可以更好的使用same_as,比如继承的处理
template <typename T>
static constexpr auto AbFactory = AbstractFactory{
 []<typename T>(const T&t)requires IsAbstractAnimal<T, Dog> { return new BigDog; },
 []<typename T>(const T&t)requires IsAbstractAnimal<T, Cow> { return new BigCow; },
};
void TestAbFactory()
{
	auto dog = AbFactory<Dog>(Dog{});
	dog->display();
	auto cow = AbFactory<Cow>(Cow{});
	cow->display();
}
int main()
{
    TestAbFactory();
    return 0;
}

上面的工厂换个名字就可以实现不同的类型创建,现在大狗大牛,也可以有小狗小牛,花狗花牛等等。只要把工厂名字改一下,concepts改一下就OK了。同样,在原来的一些STL中,需要传入仿函数的,有的不能使用lambda表达式,比如智能指针里的删除器,现在就可以了,在c++20中,可以直接在定义里使用。类似于下面:

my_unique_ptr<int,[](int* val_ptr) {
        deletep(val_ptr);
    }> my_uptr(new int(val));

c++中的技术可以不断的在标准进步的前提完善,就看实际应用的场景了。

四、总结

从这个lambda表达式可以看出来,其实c++的标准不断统一着编程风格,而且这种风格趋向于更容易理解和更容易分析对比的方向上前进。个人觉得这是一种非常让人舒服的发展方向,一如前面的SNIFAE逐渐被concepts替代,都是这种情况。虽然对c++这门语言来说,这是一种比较难于实现的过程,但只要朝着这个方向前进,就会不断赢得更多的开发人员的支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值