前语

很快乐见到你。

类型揣度,在咱们使用模板的时分,是一个十分重要的论题。在c++11中,引入了auto关键字,也使得类型揣度不局限于模板类型揣度。举个简单的比如:

template<typename T>
void show(T t) {
    std::cout << t << std::endl;
}
int i = 1;
int& num = i;
int array[8];
show(i);   // 类型T被揣度为int
show(num); // 类型T也是被揣度为int
show(array); // 类型T被揣度为int*

当咱们传递参数给模板函数show时,编译器会依据咱们的参数类型去揣度模板类型T的类型。假如你对上面的代码的类型揣度呈现了疑问,那么阅览这篇文章能够给你带来帮助。

对类型揣度不熟悉,或许会带来一些很荫蔽的问题。例如上面的引证数据类型传递给show函数时,产生了值复制,由于T被揣度成了int类型,有些读者或许会有为什么不是int&,为何会产生复制等问题。

模板匹配

值模板

参阅以下代码:

// 模板类型T,没有前置const、volatile润饰,也没有指针、引证润饰
template<typename T>
void show(T t) {
    std::cout << t << std::endl;
}
int x = 1;
const int y = 2;
int& z = x;
const int& w = y;
show(x); // T = int, t = int
show(y); // T = int, t = int
show(z); // T = int, t = int
show(w); // T = int, t = int

代码中的模板类型只要一个T,不是T&等,我这儿称之为值模板。这种类型的模板,选用的为值复制,会移除参数的constvolatile、以及引证润饰。所以最终类型揣度的T均为int类型。形参t的类型即为T,因而其类型和T保持一致。

此模板类型的中心在于值复制,那么也不难了解,复制之后其实参润饰都变得没有意义了。

指针类型在与值模板的相关下会呈现一些或许比较含糊的联系,但实际上他也是契合上面咱们所说的逻辑的。参阅以下代码:

template<typename T>
void show(T t) {
    std::cout << t << std::endl;
}
int x = 1;
const int y = 2;
const int* p1 = &y;
const int* const p2 = &y;
int* const p3 = &x;
show(p1); // T = const int*,t = const int*
show(p2); // T = const int*,t = const int*
show(p3); // T = int*,      t = int*

主要关注到show(p2);。p2是一个指向const int的const 指针。当他匹配到T时,指针自身的const属性会被疏忽,复制指针自身。但指针的类型不会被修正,仍是指向const int数据。因而,类型揣度成果为const int*。

引证模板

参阅以下代码:

template<typename T>
void show(T& t) {
    std::cout << t << std::endl;
}
int x = 1;
const int y = 2;
int& z = x;
const int& w = y;
show(x); // T = int,      t = int& 
show(y); // T = const int,t = const int& 
show(z); // T = int,      t = int& 
show(w); // T = const int, t = const int& 

在带有左值引证的模板函数中,形参t都会被转化为左值引证类型。这儿我着重是左值引证,由于右值引证有所不同,读者需求留意一下。t的类型推到也十分契合直觉,并没有什么需求剩余解说的。

模板T则去除了引证润饰,这个好了解。因而T后面跟了&,那么他自身肯定是不带引证的。

相同的逻辑,咱们也能够了解以下模板函数的类型推导:

template<typename T>
void show(const T& t) {
    std::cout << t << std::endl;
}
show(x); // T = int,t = const int& 
show(y); // T = int,t = const int& 
show(z); // T = int, t = const int& 
show(w); // T = int, t = const int& 

形参模板添加了const润饰,那么类型T就会移除其const润饰。相对应的,t类型会添加const润饰,这不难了解是吧。

前面提到,这是一个左值引证。那么关于右值引证呢?参阅以下代码:

template<typename T>
void show(T&& t) {
    std::cout << t << std::endl;
}
int x = 1;
const int y = 2;
int& z = x;
const int& w = y;
show(x); // T = int&,      t = int& 
show(y); // T = const int&,t = const int& 
show(z); // T = int&,       t = int& 
show(w); // T = const int&, t = const int& 
show(1); // T = int,        t = int&&

形如T&&,没有const、volatile润饰,称为通用引证。通用引证能够适配左值引证与右值引证。通用引证的特性在于:他既能接纳左值引证、又能接纳右值引证

因而咱们关于上面的比如中的t类型的推导其实并不奇怪。奇怪的在于T的类型:关于右值的实参,T被正常推导成int,可是当实参是左值时,为什么会被推导成引证类型?

这其实涉及到一个概念:引证折叠。姓名很高大上,可是引证折叠很简单:T& && = T&。在c++中是不能存在引证的引证的,可是在类型推到的过程中会存储引证的引证。此时会将两个引证折叠,变成一个。所以就为什么T类型带有左值引证了。

最终留意一下,只要T&&类型的才被称为通用引证,而假如是const T&&则不是通用引证,他是一个const的右值引证,无法接纳左值引证。

指针模板

参阅以下代码:

template<typename T>
void show(T* t) {
    std::cout << &t << std::endl;
}
int x = 1;
const int y = 2;
show(&x); // T = int,      t = int* 
show(&y); // T = const int,t = const int* 

指针类型和引证类型的类型揣度逻辑简直一模一样,也比较契合咱们的直觉。

数组与函数

关于数组和函数类型参数,情况有些特殊。参阅以下代码:

template<typename T>
void show(T t) {
    //
}
template<typename T>
void showR(T& t) {
    //
}
template<typename T>
void showP(T* t) {
    //
}
int fun(int){return 0;}
int array[3] = {1,2,3};
show(fun);   // T = int(*)(int),t = int(*)(int)
show(array); // T = int*,       t = int* 
showP(fun);   // T = int(int),t = int(*)(int)
showP(array); // T = int,  t = int* 
showR(fun);   // T = int(int),t = int(&)(int)
showR(array); // T = int[3],  t = int &[3] 

关于数组,首先咱们需求明确一个概念:数组与指针是不一样的类型。。虽然在使用上很类似,但数组有长度、越界判别等,都是和指针不同的。

当咱们把一个数组目标传递给T类型时,数组类型会转化为指针类型,因而模板确定为int*。相同的逻辑,关于T*模板,自然模板也就会被初始化为int

函数是类似的,函数目标也会被转化成指针,因而模板T会被初始化为指针类型,而指针类型的模板会被初始化为函数自身的类型。

最终再看到引证类型的模板。数组与函数目标,传递给引证类型的模板时,不会被退化成指针类型,而是会成为他本来的类型,且数组自身的长度也会被保存。咱们能够利用这种方式来获取到数组的长度:

template<typename T, std::size_t N>
std::size_t arraySize(T (&u)[N])
{                                                      
    return N; 
} 
int intArray[2]{0,1};
auto size = arraySize(intArray);

auto匹配

c++11添加了auto关键字,将类型揣度不仅局限于模板中。如以下代码:

auto num = 1;        // int
auto& numRef = num;  // int&
auto* numPtr = &num; // int*
auto temp = numRef;  // int

在类型揣度逻辑上,和模板基本是一模一样的,没有不同。仅有的不同在于:

auto list = {1,2,4}; // initialiez_list<int>

关于花括号数据,auto会直接揣度为initialiez_list<Type>,而模板是无法揣度的。如以下代码是非法的:

template<typename T>
void show(T t){}
show({1,2,3}); // 无法揣度T的类型

此外,auto还能够用于自定义函数返回值。如下:

// c++11
auto show() -> int {
    // ...
}
// c++14 auto被揣度为int
auto show() {
    // ...
    return 1;
}

在c++11中,咱们需求后置指明auto的类型,而在c++14中,则能够通过最终一行代码来揣度返回值类型。在c++11中,这似乎效果不是很大,可是咱们能够结合模板和decltype来完成一些更加奇特的玩法:

template<typename T>
auto show(T t) -> decltype(Convert(t)) {
    // ...
}

这儿auto的类型为Convert(t),能够依据参数类型来决议函数的返回值类型。

总结

类型揣度是c++模板编程中一个比较重要的内容,通过前面的学习,读者应该基本能掌握这部分的内容。在实际的开发中,有时分咱们不确定其所揣度的类型,能够通过开发IDE、typeid等手段来判别类型,辅助咱们确定具体的类型。

全文到此,原创不易,觉得有帮助能够点赞收藏评论转发。 有任何想法欢迎评论区沟通指正。 如需转载请评论区或私信沟通。
另外欢迎光临笔者的个人博客:传送门