模板类型推导
写了这么久C++都没思考过模板推导的相关内容,这次学习Effective Modern C++记录一下
已知模板函数:1
2
3
4template<typename T>
void f(ParamType param);
f(expr); //使用表达式调用f
在编译期间,编译器使用expr
进行两个类型推导:一个是针对T
的,另一个是针对ParamType
的。这两个类型通常是不同的,因为ParamType
包含一些修饰,比如const
和引用修饰符
如:1
2template<typename T>
void f(const T& param); //ParamType是const T&
所以根据ParamType
的类型存在三种情况:
ParamType是指针或者引用(非通用引用)
规则:
- 如果
expr
的类型是一个引用,忽略引用部分 然后
expr
的类型与ParamType
进行模式匹配来决定T
引用类型
1
2
3template<typename T>
void f(T& param); //param是一个引用
f(expr);在这里
ParamType
是T&
也就是引用类型,并且param一定是个引用,但是得看是const &还是非const &1
2
3
4
5
6
7int x=27; //x是int
const int cx=x; //cx是const int
const int& rx=x; //rx是指向作为const int的x的引用
f(x); //T是int,param的类型是int&
f(cx); //T是const int,param的类型是const int&
f(rx); //T是const int,param的类型是const int&int x 和 f(x)
推导出T是int,param的类型是int&。这个很好理解,因为本来就是T&,生成的模板函数肯定也是带引用的,int就推出intconst int cx 和 f(x)
推导出T是const int,param的类型是int&。这个也好理解和合理,因为用户向f传入const对象时就是想对象保持不可改变性,编译器自然也要满足这种需求,所以也带constconst int& rx 和 f(x)
推导出T是const int,param的类型是const int&。这个符合第一点要求,引用部分被忽略,忽略后结果和2相同
测试:
在vs2022上写代码使用boost库里面的type_id_with_cvr进行测试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
using namespace std;
//函数模板
template<typename T>
void f(T& param)
{
using std::cout;
using boost::typeindex::type_id_with_cvr;
//显示T
cout << "T = "
<< type_id_with_cvr<T>().pretty_name()
<< '\n';
//显示param类型
cout << "param = "
<< type_id_with_cvr<decltype(param)>().pretty_name()
<< '\n';
}
int main()
{
int x = 27; //x是int
const int cx = x; //cx是const int
const int& rx = x; //rx是指向作为const int的x的引用
f(x); //T是int,param的类型是int&
f(cx); //T是const int,param的类型是const int&
f(rx); //T是const int,param的类型是const int&
}
此外,如果ParamType是const T&
时,情况会有一些不同,但是情况和上面类似,不细讲1
2
3
4
5
6
7
8
9
10template<typename T>
void f(const T& param); //param现在是reference-to-const
int x = 27; //如之前一样
const int cx = x; //如之前一样
const int& rx = x; //如之前一样
f(x); //T是int,param的类型是const int&
f(cx); //T是int,param的类型是const int&
f(rx); //T是int,param的类型是const int&
指针类型
引用是另类的指针,理解上面引用后,指针也好理解,只要注意const会保持就行1
2
3
4
5
6
7
8template<typename T>
void f(T* param); //param现在是指针
int x = 27; //同之前一样
const int *px = &x; //px是指向作为const int的x的指针
f(&x); //T是int,param的类型是int*
f(px); //T是const int,param的类型是const int*
vs2022 上运行结果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
using namespace std;
//函数模板
template<typename T>
void f(T& param)
{
using std::cout;
using boost::typeindex::type_id_with_cvr;
//显示T
cout << "T = "
<< type_id_with_cvr<T>().pretty_name()
<< '\n';
//显示param类型
cout << "param = "
<< type_id_with_cvr<decltype(param)>().pretty_name()
<< '\n';
}
int main()
{
int x = 27; //x是int
const int cx = x; //cx是const int
const int& rx = x; //rx是指向作为const int的x的引用
f(x); //T是int,param的类型是int&
f(cx); //T是const int,param的类型是const int&
f(rx); //T是const int,param的类型是const int&
}
ParamType是通用引用
通用引用是universal Reference翻译过来的,也有叫万能引用的,形式上和右值引用一样(&&),第一次接触这玩意还是实习的时候师兄跟我讲的,后面就回去了解了一下。这里默认大伙已经了解左值右值哈。
规则:
- 如果
expr
是左值,T
和ParamType
都会被推导为左值引用。需要注意的是右值引用也是左值(T唯一一种被推导为引用的情况) 如果
expr
是右值,就有点类似上面的内容(右值版推导)- 如果
expr
的类型是一个引用,忽略引用部分 - 然后
expr
的类型与ParamType
进行模式匹配来决定T
这里ParamType是T&& ,param最终一定是个引用,但是得看是左值引用& 还是右值&&1
2
3template<typename T>
void f(T&& param); //param现在是一个通用引用类型
f(expr)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18int x=27; //如之前一样
const int cx=x; //如之前一样
const int & rx=cx; //如之前一样
f(x); //x是左值,所以T是int&,
//param类型也是int&
f(cx); //cx是左值,所以T是const int&,
//param类型也是const int&
f(rx); //rx是左值,所以T是const int&,
//param类型也是const int&
f(std::move(x)); // 右值引用也是左值,所以T是const int&,
//param类型也是int&
f(27); //27是右值,所以T是int,
//param类型就是int&&
- 如果
int x 和 f(x)
推导出T是int &,param的类型是int&。模板函数应该是这样void f(int& && param),,然后通过引用折叠变成void(int& param)const int cx 和 f(x)
推导出T是const int&,param的类型是int&。同上,不过加了const。const int& rx 和 f(x)
推导出T是const int&,param的类型是const int&。同上27 和 f(27)
推导出T是int,param的类型是int&&。模板函数是这样void(int&& param),使得f可以接受右值进行传入
说实话这个通用引用还挺奇妙,最终f可以接受所有值进行传入。
代码和运行结果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
using namespace std;
//函数模板
template<typename T>
void f(T&& param)
{
using std::cout;
using boost::typeindex::type_id_with_cvr;
//显示T
cout << "T = "
<< type_id_with_cvr<T>().pretty_name()
<< '\n';
//显示param类型
cout << "param = "
<< type_id_with_cvr<decltype(param)>().pretty_name()
<< '\n';
}
int main()
{
int x = 27; //如之前一样
const int cx = x; //如之前一样
const int& rx = cx; //如之前一样
f(x); //x是左值,所以T是int&,
//param类型也是int&
f(cx); //cx是左值,所以T是const int&,
//param类型也是const int&
f(rx); //rx是左值,所以T是const int&,
//param类型也是const int&
f(std::move(x)); // 右值引用也是左值,所以T是const int&,
//param类型也是int&
f(27); //27是右值,所以T是int,
//param类型就是int&&
}
ParamType既不是指针也不是引用
大白话值传递1
2
3template<typename T>
void f(T param); //以传值的方式处理param
f(expr)
规则:
- 和之前一样,如果
expr
的类型是一个引用,忽略这个引用部分 - 如果忽略
expr
的引用性(reference-ness)之后,expr
是一个const
,那就再忽略const
。如果它是volatile
,也忽略volatile
指针可能有些不一样,const 指针传进去,推导出来还是const ,但是 *const传进去const特性会被忽略,也就是说指针是可以更改指向的。1
2
3
4
5
6
7
8
9
10
11
12
13int x = 27; //如之前一样
const int cx = x; //如之前一样
const int& rx = cx; //如之前一样
const int* px = &x; //px是指向作为const int的x的指针
int* const px1 = &x;
const int* const px2 = &x;
f(x); //T和param的类型都是int
f(cx); //T和param的类型都是int
f(&x); //T和param的类型都是int*
f(px); //T和param的类型都是cosnt int*
f(px1); //T和param的类型都是 int*
f(px2); //T和param的类型都是cosnt int*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
using namespace std;
//函数模板
template<typename T>
void f(T param)
{
using std::cout;
using boost::typeindex::type_id_with_cvr;
//显示T
cout << "T = "
<< type_id_with_cvr<T>().pretty_name()
<< '\n';
//显示param类型
cout << "param = "
<< type_id_with_cvr<decltype(param)>().pretty_name()
<< '\n';
}
int main()
{
int x = 27; //如之前一样
const int cx = x; //如之前一样
const int& rx = cx; //如之前一样
const int* px = &x; //px是指向作为const int的x的指针
int* const px1 = &x;
const int* const px2 = &x;
f(x); //T和param的类型都是int
f(cx); //T和param的类型都是int
f(&x); //T和param的类型都是int*
f(px); //T和param的类型都是cosnt int*
f(px1); //T和param的类型都是 int*
f(px2); //T和param的类型都是cosnt int*
}其他
存在一些特列,记录一下数组实参
一般来说,数组传参时是退化为指针,推导的时候也应该影响不大1
2
3
4
5
6
7template<typename T>
void f(T param); //以传值的方式处理param
const char name[] = "J. P. Briggs"; //name的类型是const char[13]
const char* ptrToName = name; //数组退化为指针
f(name); //name是一个数组,但是T被推导为const char*
f(ptrToName);
但是在ParamType作为引用时,就可以有指向数组的引用了,并且可以获得数组的大小1
2
3
4template<typename T>
void f(T& param); //传引用形参的模板
const char name[] = "J. P. Briggs"; //name的类型是const char[13]
f(name); //name是一个数组,但是T被推导为const char (&)[13]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
using namespace std;
//函数模板
template<typename T>
void f(T& param)
{
using std::cout;
using boost::typeindex::type_id_with_cvr;
//显示T
cout << "T = "
<< type_id_with_cvr<T>().pretty_name()
<< '\n';
//显示param类型
cout << "param = "
<< type_id_with_cvr<decltype(param)>().pretty_name()
<< '\n';
}
int main()
{
const char name[] = "J. P. Briggs"; //name的类型是const char[13]
f(name);
}
应用:
编译器就可以获得数组大小,不过sizeof好像也可以1
2
3
4
5
6
7
8
9
10
11//在编译期间返回一个数组大小的常量值(//数组形参没有名字,
//因为我们只关心数组的大小)
template<typename T, std::size_t N> //关于
constexpr std::size_t arraySize(T (&)[N]) noexcept //constexpr
{ //和noexcept
return N; //的信息
} //请看下面
int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 }; //keyVals有七个元素
int mappedVals[arraySize(keyVals)]; //mappedVals也有七个函数实参
函数和数组类似,都会退化为指针1
2
3
4
5
6
7
8
9
10
11
12
13void someFunc(int, double); //someFunc是一个函数,
//类型是void(int, double)
template<typename T>
void f1(T param); //传值给f1
template<typename T>
void f2(T & param); //传引用给f2
f1(someFunc); //param被推导为指向函数的指针,
//类型是void(*)(int, double)
f2(someFunc); //param被推导为指向函数的引用,
//类型是void(&)(int, double)