这篇文章的名字可能不太好理解,因为我也不太能很好的给这个操作起个名字,所以就模仿数学中的“偏”给它叫作偏操作了。
问题起因
在刷题过程中,我们或许会遇到一些要把数据聚合在一起的情况,也就是使用struct
或者tuple
的情况。如果仅仅只是保存数据,我个人是习惯用tuple
,人比较懒,这样可以少起些名字而且还自带了重载比较运算符。不过默认重载的比较运算符是一个元素一个元素的比较的,很多时候并不好用,比如如果我们只想以某一个位置的元素为关键字比较,假设有一组二元组V
,要以第1个下标为关键字排序,可能我们就要这样写
1 | std::sort(V.begin(),V.end(),[](auto&a,auto&b){return std::get<1>(a)<std::get<1>(b);}); |
那个lambda看起来是不是又长又蠢,实现的功能很简单,但是格式非常冗长,而且你都已经用tuple
了,一个程序里面不可能只需要一次这样的操作,你可能在后面还要写无数个这样简单又冗长的lambda,像我这样的懒人是不能接受的。
使用模板
模板是C++的精髓,如果会使用模板,就能使代码变得简单而优雅。不过我也只是在网上看过一点模板的内容,对模板并不了解,所以后面的代码可能存在很多我也不清楚的隐患,所以如果要使用需谨慎。
我们要做的事情很简单,用一句话说就是,对两个tuple
的某个位置的元素进行二元操作。这么看我们需要的或许就是一个模板,传入类型T
、位置N
和操作OP
,然后能够实例化出一个函数对象,之后经过我各种查资料和模仿,得到了最初版本,
1 | template<typename T,int N,class OP> |
我给它命名为T_OP
,T可以理解为tuple的缩写,OP是操作的缩写,合在一起就是对tuple进行操作,也可以说是TOP!
T_OP
是一个结构体,重载了括号运算符,也叫函数对象。在重载括号运算符时返回对两个tuple
指定元素调用OP
后的结果,返回类型自动推断就可以了。
按照之前的例子简单的测试一下:
想象中的一样,按照下标1的元素进行排序了,好了收工,之前还有一点小问题。
tuple和单值操作
最初版本的T_OP
对于两个tuple
的操作已经实现了,但是试想如果我们要在lower_bound
中使用,找到下标1大于等于4的元素,如果按照最初的版本可能会写成这样
1 | auto it=lower_bound(V.begin(),V.end(),{0,4},T_OP<T,1,less<int>>{}); |
这样看着也很蠢,我们需要的只是一个4但是却要写了个大括号把其他值都写了。所以我们需要的是一个值能和tuple
比较的函数,继续对括号运算符进行重载。
1 | template<typename T,int N,class OP> |
tuple_element_t<N,T>
是T
的第N
个下标的类型通过这种方法能够重载出tuple
和单值操作的版本的函数。注意参数的顺序,后面是在经历了各种尝试之后得出的完整版本。
1 | template<typename T,int N,class OP> |
至于为什么还要重载两个参数都是单值的版本的,refuce
函数是并行的,它会并行计算部分结果之后再将各部分结果相加,假设是并行的对int
求和,最后合并的时候肯定会出现两个int
相加的情况。不过准确来说,如果要用于reduce
,第四个重载的参数类型也不是第N
个下标的类型,应该是OP
的返回类型。
结尾
上面模板里面虽然写了四个重载,但是实际编译出来只会对用到的函数进行实例化,如果没有用到是不会实例化的,这个可以用gdb
的info functions
查看实例化的函数。
最后按照惯例上个实战截图,证明这个东西真的能用