auto占位符
学过C语言的同学都知道auto是用来声明变量为自动类型的,尽管如此,auto很少被C工程师使用。因此在C++11标准赋予了auto新的含义:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符
auto使用限制
在使用auto占位符声明变量的时候必须初始化变量。例如以下例子就会报错
auto i;
i = 5; // 编译失败
auto的推导规则
1.当用一个auto关键字声明多个变量的时候,编译器遵从由左往右的推导规则,以最左边的表达式推断auto的具体类型
int n = 5;
auto *pn = &n, m = 10;
//auto *pn = &n, m = 10.0;// 编译失败,声明类型不统一
2.当使用条件表达式初始化auto声明的变量时,编译器总是使用表达能力更强的类型
auto i = true ? 5 : 8.0;// i的数据类型为double
3.如果auto声明的变量是按值初始化,且没有使用引用,也没有使用指针,则推导出的类型会忽略cv限定符
const int o = 5;
auto p = o; // auto推导类型为int,而非const int
auto &q = o; // auto推导类型为const int,q推导类型为const int&
auto *r = &o; // auto推导类型为const int,r推导类型为const int*
const auto s = p; // auto推导类型为int,s的类型为const int
4.使用auto声明变量初始化时,目标对象如果是引用,则引用属性会被忽略
int t = 5;
int &u = t;
auto v = u; // auto推导类型为int,而非int&
5.使用auto和万能引用声明变量时,对于左值会将auto推导为引用类型
int w = 5;
auto&& x = w; // auto推导类型为int& (这里涉及引用折叠的概念)
auto&& y = 5; // auto推导类型为int
6.使用auto声明变量,如果目标对象是一个数组或者函数,则auto会被推导为对应的指针类型
int z[5];
auto a = z; // auto推导类型为int*
auto b = sum; // auto推导类型为int(*)(int,int)
auto在不同C++标准中的差异
C++11标准
支持在声明静态成员变量时使用auto声明并初始化,但前提是auto必须使用const限定符
struct sometype {
auto i = 5;
};// 错误,无法编译通过
struct sometype {
static const auto i = 5; //C++11
}; //正确
C++14标准
在C++14标准支持对返回类型声明为auto的推导
auto sum(int a1, int a2) { return a1 + a2; }
在C++14中,auto可以为lambda表达式声明形参
auto l = [](auto a1, auto a2) { return a1 + a2; };
auto retval = l(5, 5.0);
C++17标准
在C++11标准中,auto修饰静态成员变量时必须使用const修饰,这会导致成员变量常量化,为了解决这个问题,在C++17标准中,对于静态成员变量,auto可以在没有const的情况下使用
struct sometype {
static auto i = 5; // C++17
};
C++17标准对auto关键字又一次进行了扩展,使它可以作为非类型模板形参的占位符
#include <iostream>
template<auto N>
void f()
{
std::cout << N << std::endl;
}
int main()
{
f<5>(); // N为int类型
f<'c'>();// N为char类型
f<5.0>();// 编译失败,模板参数不能为double
}
C++20标准
C++20之前的标准,无法在函数形参列表中使用auto声明形参
void echo(auto str) {…} // C++20之前编译失败,C++20编译成功
decltype说明符
使用限制
上一章节,我们介绍了auto,得知auto不可以在非静态成员变量上使用,在C++20之前不可以在函数形参列表中使用,但是上述这些限制对于decltype说明符都不存在
struct S1 {
int x1;
decltype(x1) x2;
double x3;
decltype(x2 + x3) x4;
};
int x1 = 0;
decltype(x1) sum(decltype(x1) a1, decltype(a1) a2)
{
return a1 + a2;
}
auto x2 = sum(5, 10);
看到这里,我们感觉decltype好像没有什么用,但事实上decltype主要的用处在函数返回类型后置,库函数的编写者可能使用的更多。
decltype的推导规则
1.如果e是一个未加括号的标识符表达式(结构化绑定除外)或者未加括号的类成员访问,则decltype(e)推断出的类型是e的类型T。如果并不存在这样的类型,或者e是一组重载函数,则无法进行推导。
2.如果e是一个函数调用或者仿函数调用,那么decltype(e)推断出的类型是其返回值的类型。
3.如果e是一个类型为T的左值,则decltype(e)是T&。
4.如果e是一个类型为T的将亡值,则decltype(e)是T&&。
5.除去以上情况,则decltype(e)是T。
6. 通常情况下,decltype(e)所推导的类型会同步e的cv限定符,但是还有其他情况,当e是未加括号的成员变量时,父对象表达式的cv限定符会被忽略,不能同步到推导结果。
int i;
int *j;
int n[10];
decltype(i=0) a = i; //规则3
decltype(0,i) b = i; //规则3
decltype(i,0) c; //规则5
decltype(n[5]) d = i; //规则3
decltype(*j) e= i; //规则5
// decltype(static_cast<int&&>(i)) iii; //规则4
decltype(i++) g; //规则5
decltype(++i) h= i; //规则3
const int ia = 0;
decltype(ia); // 规则6 decltype(i)推导类型为const int
struct A {
double x;
};
const A* aa = new A();
decltype(aa->x); //规则6 decltype(a->x)推导类型为double, const属性被忽略
decltype((aa->x));//规则3+6 decltype((aa->x))推导类型为const double&
使用场景
C++11中函数返回类型后置
在C++11标准中auto作为占位符并不能使编译器对函数返回类型进行推导,所以就有了以下用法
auto sum(int a1, int a2)->int
{
return a1+a2;
}
template<class T1, class T2>
auto sum(T1 a1, T2 a2)->decltype(a1 + a2)
{
return a1 + a2;
}
auto x4 = sum(5, 10.5);
需要推导函数返回值为引用类型的情况
在C++14中auto已经可以对函数返回类型进行推导,那么decltype是不是就没有存在的意义了,答案当然是否定的了,例如以下代码
template<class T>
auto return_ref(T& t)
{
return t;
}
int x1 = 0;
static_assert(
std::is_reference_v<decltype(return_ref(x1))>
);// 编译错误,返回值不为引用类型
根据auto推导规则4,该函数返回的类型是T类型,假如我们实际需要它返回T&类型,这个时候就需要我们的decltype上场了(规则1)
template<class T>
auto return_ref(T& t)->decltype(t)
{
return t;
}
int x1 = 0;
static_assert(
std::is_reference_v<decltype(return_ref(x1))>
);
decltype(auto)
在C++14标准中出现了decltype和auto两个关键字的结合体:decltype(auto)。
它的作用简单来说,就是告诉编译器用decltype的推导表达式规则来推导auto。另外需要注意的是,decltype(auto)必须单独声明,也就是它不能结合指针、引用以及cv限定符