模板类型推导
template <typename T>
void f(ParamType param);
f(expr)
- ParamType具有指针或引用类型,但不是一个万能引用
a. 若expr具有引用类型,现将引用类型忽略
b. 然后,对expr类型和ParamType的类型进行模式匹配,来决定T的类型
c. 当人们向引用类型的形参传入const对象时,他们希望该对象保持其不可修改的属性,也就是说希望形参成为const的引用类型。这也是为何向持有T&类型的模板传入一个const对象是安全的:对象的常量性会成为T的类型推到结果的组成部分
c. 如果形参为右值,同样按照以上方式处理
d. 如果expr是个指针类型,同引用类型处理 - ParamType是一个万能引用
a. 如果expr是一个左值,T和ParamType都会被推导为左值引用,这个结果具有双重奇特之处:首先,这是在模板类型推导中,T被推导为引用类型的唯一情况。其次,尽管在声明时使用的是右值引用的语法,他的类型推导结果却是左值引用
b. 如果expr是个右值,则应用第一条规定 - ParamType既不是指针也不是引用
a. 如果expr具有引用类型,则忽略其引用部分
b. 忽略expr的常量性,易失性
c. 指向常量字符串的常量指针,将会推导为指向常量字符串的非常量指针 - 在数组的按值传递的类型推导过程中,数组将会被推导成指针类型,但是可以将形参声明为数组的引用,这样参数的类型就是数组类型了(包括数组的大小)
- 函数实参同条款四
auto类型推导
C++11中auto不再是一个存储类型指示符,而是用于自动类型推导,从初始化表达式中推断出变量的数据类型。从这个意义上讲,auto并非一种“类型”声明,而是一个类型声明时的“占位符”,编译器在编译时期需要根据初始化表达式来推导auto的实际类型并替换为变量实际的类型。所以使用auto定义变量时必须对其进行初始化
template <typename T>
void f(ParamType param);
f(expr)
- 在一般情况下,auto类型推导和模板类型推导是一模一样的,但是auto类型推导会假定使用大括号括起的初始化表达式代表一个std::initializer_list,但是模板类型推导不会这样
- 在auto类型推导的时候,当某变量使用auto来声明时,auto就扮演了模板中的T这个角色,而变量的类型饰词则扮演的就是 ParamType这个角色。当需要进行类型推导时,编译器的行为仿佛对应于每个声明生成了一个模板和一次使用对应初始化表达式针对该模板的调用一样。
- 当用于auto声明的初始化表达式使用大括号括起来的时候,推导所得的类型就是std::initializer_list的类型,但是如果向函数模板传入一个同样的初始化表达式,类型推导就会失败(除非将模板形参声明为std::initialize_list< T >)。
- 当用于auto声明的初始化表达式使用大括号括起来的时候,会涉及到两种类型推导,一种为推导auto所代表的类型,这时应用auto推导规则,另一种为将大括号及其内容推导出std::initializer_list中的类型参数,这时引用模板类型推导规则。如果大括号内参数类型不一致,将会在第二种类型推导时报错
- 在函数返回值或者lambda的形参中使用auto,将会是使用模板类型推导而不是auto类型推导,所以return {1,2,3};将会导致编译错误
#include <iostream>
#include <vector>
#include <string>
using namespace std;
double foo() {}
void func(vector<string> & tmp)
{
for (auto i = tmp.begin(); i < tmp.end(); i++)
{
// 一些代码
}
}
int main()
{
auto x = 1; // x的类型为int
auto y = foo(); // y的类型为double
struct m { int i; }str;
auto str1 = str; // str1的类型是struct m
auto z; // err, 无法推导,无法通过编译
z = x;
return 0;
}
auto不能推导的场景
- auto不能作为函数的参数
- auto不能直接用来声明数组
- auto不能定义类的非静态成员变量
- 实例化模板时不能使用auto作为模板参数
auto注意点
- 用auto声明指针类型时,用auto和auto *没有任何区别,但是auto声明引用类型时必须加&。
- 当在同一行定义多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
- 为了避免与C++98的auto发生混淆,C++11只保留了auto作为类型指示符的用法
- auto常用于新式for循环以及lambda表达式
decltype
- decltype的主要用途在于声明那些返回值类型依赖于形参类型的函数模板
- 绝大多数情况下,decltype会得出变量或表达式的类型而不作任何修改
- 对于类型为T的左值表达式,除非该表达式仅有一个名字,decltype总是得出T&类型
- C++14支持decltype(auto) ,和auto一样,他会从其初始化表达式出发来推导类型,但是他的类型推导使用的是decltype的规则
- decltype(auto)并不仅限于函数返回值类型上,在变量声明的场合上,如果你想要在初始化表达式处应用decltype类型推导规则也可以
- 当decltype应用在一个不仅仅包含名字的左值表达式的时候,decltype总是保证得出的类型是一个左值引用。因此当使用decltype(auto)的时候一定要注意这种情况
int x = 0;
decltype((x)); // 推导的结果是int &
#include <typeinfo>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int i;
decltype(i) j = 0;
cout << typeid(j).name() << endl; // 打印出"int", g++表示integer
float a;
double b;
decltype(a + b) c;
cout << typeid(c).name() << endl; // 打印出"double", g++表示double
vector<int> vec;
typedef decltype(vec.begin()) vectype; // decltype(vec.begin()) 改名为 vectype
vectype k; // 这是auto无法做到的
//decltype(vec.begin()) k; // 这是auto无法做到的
for (k = vec.begin(); k < vec.end(); k++)
{
// 做一些事情
}
enum {Ok, Error, Warning}flag; // 匿名的枚举变量
decltype(flag) tmp = Ok;
return 0;
}
C++11中的返回值类型尾序语法
返回类型后置:在函数名和参数列表后面指定返回类型。
int func(int, int);
auto func2(int, int) -> int;
template<typename T1, typename T2>
auto sum(const T1 & t1, const T2 & t2) -> decltype(t1 + t2)
{
return t1 + t2;
}
template <typename T1, typename T2>
auto mul(const T1 & t1, const T2 & t2) -> decltype(t1 * t2)
{
return t1 * t2;
}
int main()
{
auto a = 3;
auto b = 4L;
auto pi = 3.14;
auto c = mul( sum(a, b), pi );
cout << c << endl; // 21.98
return 0;
}