笔记要点
模板类型推导
模板的定义:
template <typename T>
func(ParamType args)
(1)类型推导Param包含普通的引用或指针的情况
template <typename T>
func(T& args)
const int& exp = 10;
func(exp); //Param类型为 const int&
//T类型为const int
const int exp = 10;
func(exp);//Param类型为 const int&
//T类型为const int
int exp = 10;
func(exp);//Param类型为 int&
//T类型为int
Note:
1、推导结果会忽略exp的引用或指针,然后根据忽略引用或指针特性的exp去推导T。
2、同时保留exp的CV特性(即const、violatile)。
(2)类型推导Param为万能引用的情况
template <typename T>
func(T&& args)
const int exp = 10;
func(exp);//Param类型为const int&
//T类型为const int&
const int& exp = 10;
func(exp);//Param类型为const int&
//T类型为const int&
int exp = 10;
func(exp);//Param类型为int&
//T类型为int&
func(10);//Param类型为int&&
//T类型为int
Note:
1、左值被推导为左值引用,如果是右值则按照(1)种的法则进行。
2、在模板类型中,T唯一被推导成引用类型。这里有个小细节,上面模板前3种情况下模板
实例化的结果如下所示:
func(int& && args);
在C++是不允许有对引用进行引用,因此这里涉及到一个机制,引用的折叠(模板实例化是是其中之一)。
(3)类型推导Param为既不是引用也不是指针的情况
template <typename T>
func(T args) //args pass-by-value
const int exp = 10;
func(exp);//Param类型为int
//T类型为int&
const int& exp = 10;
func(exp);//Param类型为int
//T类型为int
int exp = 10;
func(exp);//Param类型为int
//T类型为int
func(10);//Param类型为int
//T类型为int
Note:这里模板实例化的机制是实参通过拷贝的形式传递给args,即此时实参与形参是不用的对象。因此存在以下两条规则:
1、如果exp包含CV特性,则忽略exp的CV特性
2、同理exp是引用类型,则忽略exp的CV特性
corn case(指向const内容的const指针)
template <typename T>
func(T args) //args pass-by-value
const char* const names= "Xiao Wang";
func(names);//Param类型为 const char*
//T类型为 const char*
Note:这里nams是一个指向const字符串的const指针。当传递给func时,由于对这个const指针进行拷贝操作,这个指针的内存bit被拷贝给param,因此指针的const被忽略,而字符串的const保留下来。
corn case (数组退化、函数退化)
template <typename T>
func(T args) //args pass-by-value
const char names[10]= "Xiao Wang";//类型为 const char[]
func(names);//Param类型为 const char*
//T类型为 const char*
void somefunc(int,double);
func(somefunc); //Param类型为 void (*)(int,double)
Note: names的类型为const char[],然而在类型推导过程中退化成指针const char*。同理函数也会退化成函数指针。
template <typename T>
func(T& args) //参数按照引用传递
const char names[10]= "Xiao Wang";//类型为 const char[]
func(names);//Param类型为 const char (&)[10]
//T类型为 const char (&)[10]
void somefunc(int,double);
func(somefunc); //Param类型为 void (&)(int,double)
auto类型推导
auto x = {10,5,6}; //x的类型 std::initializer_list
template <typename T>
void func(T args);
func({10,5,6});//编译器无法推导类型
Note:
auto类型推导与模板类型推导类似,除了针对花括号初始化成std::initializer_list,但是模板类型不能通过编译。
template <typename T>
auto func(Param args);
Note:在c++14中,auto作为函数或lambda返回类型,类型推导机制与模板类型推导一致。
decltype的理解
用法:给定变量或表达式,decltype会告诉变量或表达式的类型。
推导四个规则:decltype(e)
1、如果e是不带括号的标记符表达式或类成员表达式,推导结果是e所命名实体的类型。如果e是一个重载的函数,则编译错误。(标记符表达式是除去关键字、字面量等编译器需要使用的标记之外的程序员自己定义的标记,而单个标记符对应的表达式即为标记符表达式。)
2、如果e是个将亡值,则推导为T&&。
3、如果e是个左值,则推导为T&。
4、如果e是个纯右值,则推导为T。
场景应用:假设我们想写一个函数,这个函数中接受一个支持方括号索引(也就是"[]")的容器作为参数,验证用户的合法性后返回索引结果。这个函数的返回类型与函数参数有关,则可以通过c++11尾随返回类型技术,如下所示:
template <typename container,typename T>
auto func(container& c,T i) -> decltype(c[i]){
...
return c[i];
}
在c++14中,上述可以去掉尾随返回类型,直接用auto进行表示:
template <typename container,typename T>
auto func(container& c,T i){
...
return c[i];
}
然而这样的代码又要引发新的问题,由于对于大多数容器进行[]操作,都会返回**T&**类型,而auto在函数返回类型推导与模板推导一致,因此上述代码会忽略引用特性,使得返回一个右值。
std::deque<int> d;
....
func(d,5) = 10;//我们希望修改d[5]的值,然而函数返回右值,导致编译错误
为了达到我们预期的效果,我们需要将返回值运用decltype方法,即func()返回的类型是d[i]的类型。因此可以利用decltype(auto)形式,使得auto推导类型应用decltype规则。
在应用decltype(auto)时,应注意在返回值添加()会导致意想不到的结果。
decltype(auto) f1()
{
int x = 0;
...
return x; // decltype(x) is int, so f1 returns int
}
decltype(auto) f2()
{
int x = 0;
return (x); // decltype((x)) is int&, so f2 return int&
}
获得类型推导结果
编译器诊断
声明一个类模板,但不提供实现。
int a;
const int * b;
template <typename T>
class TD;//模板没有写相关实现。
TD<decltype(a)> test1;
TD<decltype(b)> test2;
编译报错结果:
error: aggregate 'TD<int> xType' has incomplete type and cannot be defined
error: aggregate 'TD<const int *> yType' has incomplete type and cannot be defined