C++ Generic Programming:泛型编程
泛型编程
单纯想维护一篇文章,记录一些有用或无用的泛型demo。和其他文章风格不同,本文无组织、无脉络、无思路,糟粕率很高,随缘更新记录。
从普通模板开始
使用普通模板注意以下几点。
废弃的模板导出
对于模板成员函数,C++11后基本废弃了“模板导出”特性,即应该尽量将模板成员函数的声明和定义放在头文件(内联实现),而并非单独在源文件给出模板的实现,因为编译器的复杂性因此大大增加甚至不再支持;
自动推导
模板类型可以放在参数、返回值或者函数体内: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
using namespace std;
template<typename T1,typename T2, typename T3>
T3 add(T1 a,T2 b){
return a+b;
}
int main(){
int a = 2;
float b = 3.14;
cout<<add<int,float,float>(a,b);
return 0;
}
上述例子缺省任意显式类型都会被报错,因为传统的template仅支持有限的类型推导,如下例子:此处a+b的类型并不重要,因为最后结果都会被强制转换成T3(假如不满足加法类型或者类型转换编译器负责报错),所以调用时仅需要指定一个类型,这个不可推导的类型必须放在第一个模板参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using namespace std;
template<typename T3,typename T1,typename T2>
T3 add(T1 a,T2 b){
return static_cast<T3>(a+b);
}
int main(){
int a = 2;
float b = 3.14;
cout<<add<int>(a,b);
return 0;
}
C++11以后因为引入了decltype类型推导和匿名函数,支持这样的推导:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
using namespace std;
template<typename T1,typename T2, typename T3 = decltype(T1()+T2())> //T3如果显式指定,不再使用自动推导
T3 add(T1 a,T2 b){
return a+b;
}
int main(){
int a = 2;
float b = 3.14;
cout<<add<int,float>(a,b);
return 0;
}
非类型参数
模板函数的意义是定义泛型的函数行为,因此常见的template参数一般是typename或class,这类参数称为类型参数;template也支持非类型参数,这些参数必须是整数类型,包括int/bool/普通指针/函数指针等,绝不能是浮点数类型,这种情况下就像在模板中定义了一种宏一样,例如(这个例子可能具有局限性,memset用于单字节填充,如果T是int等应该使用循环填值,此处仅作方便):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using namespace std;
template<typename T, int Size>
T printf(){
T array[Size];
memset((void*)array, 'x', Size); //单字节填充避免垃圾内存
return array[Size-1];
}
int main(){
cout<<printf<char,5>(); //注意不要遗漏括号,否则就是输出函数指针了
return 0;
}
对于指针复杂类型参数,template更显玄学: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using namespace std;
template<typename T1,typename T2, int(*foo)(T1,T2)>
auto printf(T1 a,T2 b) -> decltype(foo(a,b)){
return foo(a,b);
}
template<typename T1,typename T2>
int add(T1 a,T2 b){
return static_cast<int>(a+b);
}
int main(){
cout<<printf<int,int,add<int,int>>(1,2)<<endl; //3
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using namespace std;
template<typename T1,typename T2, typename T,int(T::*foo)(T1,T2)> //修改成成员函数指针
auto printf(T&obj,T1 a,T2 b) -> decltype((obj.*foo)(a,b)){
return (obj.*foo)(a,b);
}
class Person{
public:
int sub(int a,double b){
return a-b;
}
};
int main(){
Person p;
cout<<printf<int,double,Person,&Person::sub>(p,1,2.34)<<endl; //3
return 0;
}1
2
3
4
5
6
7
8
9
10
11template<typename T, int Size>
T printf(){
T array[Size];
memset((void*)array, 'x', Size); //单字节填充避免垃圾内存
return array[Size-1];
}
void test(){
int x = 5;
cout<<printf<char,x>();
}constexpr int x = 5。
编译期可变array设计
最近希望在头文件中包含一些编译期数据结构,后面发现C++
17的inline能直接作用于STL和变量,放弃了这种写法,但是弃之可惜,这个版本就放在这里了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using namespace std;
//定义一个可变长度模板
template <std::size_t N>
using VersionMd5List = std::array<std::pair<std::string_view, std::string_view>, N>;
//这个模板支持可变列表
inline constexpr VersionMd5List<1> array2{{
{"version1", "c8aedaewaewq2313"},
}};
//另一个模板自动去推导成员数量
template<typename ...Pair>
constexpr decltype(auto) make_versionMd5_pair(Pair&&...p){
return std::array<std::pair<std::string_view, std::string_view>, sizeof...(Pair)>(
{std::forward<Pair>(p)...}
);
}
constexpr auto array1 = make_versionMd5_pair(
std::pair{"version2", "ddac8aedaewaewq2313"},
std::pair{"version3", "css8aedaewaewq2313"}
);
// template <typename T>
// struct remove_cv_ref{
// using type = std::remove_cv_t<std::remove_reference_t<T>>;
// };
//想每个id对应不同的长度的列表,使用std::variant可以统一装载,decltype自动推导前需要去除cv特性和引用特性
template<typename T>
using remove_cv_ref_t = std::remove_cv_t<std::remove_reference_t<T>>;
using randomList = std::variant <KINDCLAIM(array1), KINDCLAIM(array2)>;
//创建出了id-可变列表数据结构
inline constexpr std::array<std::pair<int, randomList>, 2> screenInfo{{
{0x01, array1},
{0x02, array2}
}};
//std::visit实现的专门的打印函数
inline void printInfo(const std::array< std::pair<int, randomList>, 2>& screenInfo){
for(const auto& [code, variantList] : screenInfo){
std::cout << "id: " <<code << endl;
std::visit([](const auto& list){
for(const auto& [k,v] : list){
cout << "version: " << k << ", md5: " << v << endl;
}
},variantList);
}
}
int main(){
printInfo(screenInfo);
cout << "done" << endl;
return 0;
}

