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;
}
函数回调签名校验设计
这是一个集合了返回值类型、参数类型萃取的模板,通过类型萃取我们能对可调用对象的函数签名进行校验,以确保其参数类型和返回值类型符合我们设计期望,以下。
返回值类型提取模板
一个提取函数返回值类型的函数模板: 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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
using namespace std;
//函数原模板
template<typename F>
struct InvokeResult;
//函数特化:匿名函数/仿函数
template<typename F>
struct InvokeResult : InvokeResult<decltype(&F::operator())>{
static void DebugInfo(){
cout << "[lambda/functor specialization]:" << " ";
};
};
//普通函数指针
template<typename R, typename... Args>
struct InvokeResult<R(*)(Args...)>{
using type = R;
static void DebugInfo(){
cout << "[normal function pointer specialization]:" << " ";
};
};
//成员函数
template<typename C, typename R, typename... Args>
struct InvokeResult<R(C::*)(Args...)>
{
using type = R;
static void DebugInfo(){
cout << "[member func specialization]:" << " ";
};
};
//常成员函数
template<typename C, typename R, typename... Args>
struct InvokeResult<R(C::*)(Args...) const>
{
using type = R;
static void DebugInfo(){
cout << "[const member func specialization]:" << " ";
};
};
//可调用对象
template<typename F>
struct InvokeResult<std::function<F>>
{
using type = typename std::function<F>::result_type;
static void DebugInfo(){
cout << "[functional obj specialization]:" << " ";
};
};
/* //以下列举了若干种函数对象 */
float add(int x, float y){
return (float)x+ y;
}
class Person{
public:
int minus(double x, double y){
return round(x-y);
}
bool printf() const{
cout << "_x = " << _x << "\t" << "_y = " << _y << endl;
return true;
}
private:
double _x = 9.99;
double _y = 0.99;
};
//仿函数
struct MyFunc{
double operator()(int x, float y){
return (double)x+ y;
}
};
template<typename T>
void printType(){
cout << boost::typeindex::type_id_with_cvr<T>().pretty_name() << endl;
}
int main(int, char**){
MyFunc f1;
f1(2,3);
auto lambda = [](int x, int y)->int{
return x + y;
};
//匿名函数
using type_lambda = InvokeResult<decltype(lambda)>::type;
InvokeResult<decltype(lambda)>::DebugInfo();
type_lambda t1;
printType<type_lambda>();
//仿函数
using type_functor = InvokeResult<MyFunc>::type;
InvokeResult<MyFunc>::DebugInfo();
type_functor t2;
printType<type_functor>();
//普通函数
auto nfp = &add;
using type_normal = InvokeResult<decltype(nfp)>::type;
InvokeResult<decltype(nfp)>::DebugInfo();
printType<type_normal>();
float(*fp)(int,float) = add;
using type_normal1 = InvokeResult<decltype(fp)>::type;
InvokeResult<decltype(fp)>::DebugInfo();
printType<type_normal1>();
//成员函数
using type_member = InvokeResult<decltype(&Person::minus)>::type;
InvokeResult<decltype(&Person::minus)>::DebugInfo();
printType<type_member>();
//常成员函数
using type_cmember = InvokeResult<decltype(&Person::printf)>::type;
InvokeResult<decltype(&Person::printf)>::DebugInfo();
printType<type_cmember>();
//std::function对象
Person p1;
std::function<int(double,double)>callable = std::bind(&Person::minus,&p1, std::placeholders::_1, std::placeholders::_2);
using type_functional = InvokeResult<decltype(callable)>::type;
InvokeResult<decltype(callable)>::DebugInfo();
printType<type_functional>();
cout << "done" << endl;
return 0;
}
输出: 1
2
3
4
5
6
7[lambda/functor specialization]: int
[lambda/functor specialization]: double
[normal function pointer specialization]: float
[normal function pointer specialization]: float
[member func specialization]: int
[const member func specialization]: bool
[functional obj specialization]: int
参数/返回值类型、参数个数提取模板
我们可以拓展一下,将模板类型提取扩展至参数类型打印和参数数量统计:
这里可以引入几种语法糖,首先可以看到,普通函数指针、成员函数、常成员函数中模板就定义了Args,所以只需要用这些参数构造std::tuple类型的参数列表:
1
using params = std::tuple<Args...>;
1
2
3
4
5constexpr size_t args_nums = sizeof...(Args);
//等效于tuple接口:
constexpr size_t args_nums = std::tuple_size_v<Tuple>;
using args_type = std::tuple_element_t<N, Tuple>; //其中N代表Tuple的第N个参数std::tuple_element_t虽然能够提取参数类型,但是要求N必须是编译期常量,意味着我们的运行期代码是无法使用的:
1 | //This is a wrong code : |
编译期递归的方法可以解决这个问题:
1
2
3
4
5
6
7
8
9template<typename Tuple, size_t I=0>
void printArgsList(){
if constexpr(I < std::tuple_size_v<Tuple>){
using args_type = std::tuple_element_t<I,Tuple>;
cout << "params"<< I << " kind is :";
printType<args_type>();
printArgsList<Tuple, I+1>();
}
}
完整源码如下: 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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
using namespace std;
//函数原模板
template<typename F>
struct InvokeResult;
//函数特化:匿名函数/仿函数
template<typename F>
struct InvokeResult : InvokeResult<decltype(&F::operator())>{
static void DebugInfo(){
cout << "[lambda/functor specialization]:" << " ";
};
};
//普通函数指针
template<typename R, typename... Args>
struct InvokeResult<R(*)(Args...)>{
using type = R;
using params = std::tuple<Args...>;
static void DebugInfo(){
cout << "[normal function pointer specialization]:" << " ";
};
};
//成员函数
template<typename C, typename R, typename... Args>
struct InvokeResult<R(C::*)(Args...)>
{
using type = R;
using params = std::tuple<Args...>;
static void DebugInfo(){
cout << "[member func specialization]:" << " ";
};
};
//常成员函数
template<typename C, typename R, typename... Args>
struct InvokeResult<R(C::*)(Args...) const>
{
using type = R;
using params = std::tuple<Args...>;
static void DebugInfo(){
cout << "[const member func specialization]:" << " ";
};
};
//可调用对象
template<typename F, typename ...Args>
struct InvokeResult<std::function<F(Args...)>>
{
using type = typename std::function<F(Args...)>::result_type;
using params = std::tuple<Args...>;
static void DebugInfo(){
cout << "[functional obj specialization]:" << " ";
};
};
/* //以下列举了若干种函数对象 */
float add(int x, float y){
return (float)x + y;
}
class Person{
public:
int minus(double x, double y){
return round(x-y);
}
bool printf() const{
cout << "_x = " << _x << "\t" << "_y = " << _y << endl;
return true;
}
private:
double _x = 9.99;
double _y = 0.99;
};
//仿函数
struct MyFunc{
double operator()(int x, float y){
return (double)x+ y;
}
};
//打印类型
template<typename T>
void printType(){
cout << boost::typeindex::type_id_with_cvr<T>().pretty_name() << endl;
}
//递归打印参数列表类型(std::tuple_element_t无法使用运行期遍历,故使用编译期递归方法)
template<typename Tuple, size_t I=0>
void printArgsList(){
if constexpr(I < std::tuple_size_v<Tuple>){
using args_type = std::tuple_element_t<I,Tuple>;
cout << "params"<< I << " kind is :";
printType<args_type>();
printArgsList<Tuple, I+1>();
}
}
int main(int, char**){
MyFunc f1;
f1(2,3);
auto lambda = [](int x, int y)->int{
return x + y;
};
//匿名函数
using type_lambda = InvokeResult<decltype(lambda)>::type;
InvokeResult<decltype(lambda)>::DebugInfo();
type_lambda t1;
printType<type_lambda>();
printArgsList<InvokeResult<decltype(lambda)>::params, 0>();
//仿函数
using type_functor = InvokeResult<MyFunc>::type;
InvokeResult<MyFunc>::DebugInfo();
type_functor t2;
printType<type_functor>();
printArgsList<InvokeResult<MyFunc>::params, 0>();
//普通函数
auto nfp = &add;
using type_normal = InvokeResult<decltype(nfp)>::type;
InvokeResult<decltype(nfp)>::DebugInfo();
printType<type_normal>();
printArgsList<InvokeResult<decltype(nfp)>::params, 0>();
float(*fp)(int,float) = add;
using type_normal1 = InvokeResult<decltype(fp)>::type;
InvokeResult<decltype(fp)>::DebugInfo();
printType<type_normal1>();
printArgsList<InvokeResult<decltype(fp)>::params, 0>();
//成员函数
using type_member = InvokeResult<decltype(&Person::minus)>::type;
InvokeResult<decltype(&Person::minus)>::DebugInfo();
printType<type_member>();
printArgsList<InvokeResult<decltype(&Person::minus)>::params, 0>();
//常成员函数
using type_cmember = InvokeResult<decltype(&Person::printf)>::type;
InvokeResult<decltype(&Person::printf)>::DebugInfo();
printType<type_cmember>();
printArgsList<InvokeResult<decltype(&Person::printf)>::params, 0>();
//std::function对象
Person p1;
std::function<int(double,double)>callable = std::bind(&Person::minus,&p1, std::placeholders::_1, std::placeholders::_2);
using type_functional = InvokeResult<decltype(callable)>::type;
InvokeResult<decltype(callable)>::DebugInfo();
printType<type_functional>();
printArgsList<InvokeResult<decltype(callable)>::params, 0>();
cout << "done" << endl;
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23[lambda/functor specialization]: int
params0 kind is :int
params1 kind is :int
[lambda/functor specialization]: double
params0 kind is :int
params1 kind is :float
[normal function pointer specialization]: float
params0 kind is :int
params1 kind is :float
[normal function pointer specialization]: float
params0 kind is :int
params1 kind is :float
[member func specialization]: int
params0 kind is :double
params1 kind is :double
[const member func specialization]: bool
[functional obj specialization]: int
params0 kind is :double
params1 kind is :double
函数签名校验和返回值合法性检查
这些函数返回值常常被用于检查回调函数签名是否合法(类型是否和预想匹配),例如给定一种函数指针F,我们希望进行以下检查:
函数F是否和可调用对象类型兼容(可以构造指定类型的
std::function<R(Args...)>);函数F的返回值是否和约定返回值R兼容,或允许返回void。
借用了之前获取函数返回值的模板,以下: 1
2
3
4
5
6
7
8
9
10
11template <typename F, typename R, typename ...Args>
struct Invokable : std::is_constructible<std::function<R(Args...)>, std::reference_wrapper<typename std::remove_reference<F>::type>>{};
template<typename F, typename R>
struct Returnable : std::is_constructible<R, typename InvokeResult<std::decay_t<F>>::type>{};
template <typename F>
struct Returnable<F, void> : std::conditional_t<std::is_same<void, typename InvokeResult<std::decay_t<F>>::type>::value,std::true_type, std::false_type>{};
template<typename F, typename R, typename ...Args>
struct Matchable : std::conjunction<Invokable<F,R,Args...>, Returnable<F, R>>{};
针对这几个模板,写了一些用例如下:
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
26int main(int, char**){
auto lambda = [](int x, int y)->int{
return x + y;
};
cout << Invokable<decltype(lambda), int, int , int>::value << endl; //true,完全匹配
cout << Invokable<decltype(lambda), float, float , float>::value << endl; //true,float能隐式转为int
cout << Invokable<decltype(lambda), std::string, float , float>::value << endl; //false, 不能转字符串
Person p1;
std::function<int(double, double)> f1 = std::bind(&Person::minus, &p1, std::placeholders::_1, std::placeholders::_2);
cout << Invokable<decltype(f1), int, double, double>::value << endl; //std::function也ok
auto lambda1 = []()->int{
return 0;
};
auto lambda2 = []()->void{
return;
};
cout << Returnable<decltype(lambda1), int>::value << endl; //true
cout << Returnable<decltype(lambda2), void>::value << endl; //特化也ok
//struct Matchable只是一种条件与,忽略
cout << "done" << endl;
return 0;
}
std::conjunction/std::disjunction/std::negation
上述std::conjunction是一种C++
17引入的一种条件与:
当参数为空,相当于条件真(
std::true_type);当提供一个参数,继承该参数逻辑;
当提供多个参数,其继承
std::conditional_t递归判断,一旦遇到条件假,立刻返回假且不再编译剩下的条件内容,这被称为std::conjunction的短路求值特性。1
2
3
4
5
6
7
8template<class... B>
struct conjunction : std::true_type {};
template<class B1>
struct conjunction<B1> : B1 {};
template<class B1, class... Bn>
struct conjunction<B1, Bn...> : std::conditional_t<bool(B1::value), conjunction<Bn...>, B1> {};
所以比起手写的条件与,多个条件时尽管含非法定义,std::conjunction仍然能通过编译,例如:
1 |
|
同理,C++
17还引入了std::disjunction,其是std::conjunction的相反,对应逻辑或:
当提供空参数时,
std::disjunction返回条件假;当提供一个参数时,
std::disjunction继承该条件逻辑(同std::conjunction);当提供多个参数时,只有当全部参数为假,
std::disjunction才返回假,一旦遇到真条件,返回真并且停止剩下条件的编译,可见同样遵循短路求值特性。
C++
17引入的最后一个逻辑关系是逻辑非,为std::negation<T>::type(对应std::true_type或std::false_type)以及std::negation<T>::value或者std::negation_t<T>。

