1 auto
1.1 引入
在C++11之前,auto关键字主要用于自动存储类说明符,但在C++11中,其含义发生了改变。现在,auto关键字用于自动类型推导,由编译器根据初始化表达式的类型自动推断变量的类型。特别是在声明的变量的类型未知的情况下(比如在模板编程),auto关键字可以让编译器替我们去分析表达式所属的类型。
1.2 auto关键字基本使用方式
C++引入auto关键字主要有两种用途:
(1)在变量声明时根据初始化表达式自动推断该变量的类型。
int i = 0, &ri = i;
auto a = i; //a为int型变量
auto a1 = ri; //a1为int型变量
auto p = &i;// &i 是一个普通int指针,p是一个整型指针int *
auto p1 = &ri; //同上
(2)在声明函数时作为函数返回值的占位符。
template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y)->decltype(x*y)
{
return x*y;
}
1.3 auto关键字的推导规则
1.3.1 常规推导规则
(1)基本数据类型
编译器会根据初始化表达式的类型来推导变量的类型。
auto i = 1; // int
auto d = 2.66; // double
auto s = "test"; // const char*
(2)引用类型
auto关键字也可以用于引用类型的推导。但是,需要注意的是,当使用auto&时,编译器会保留引用的类型。
int x = 2;
auto z = x; // int
auto& y = x; // int&
auto h = y; // int
(3)常量类型
当使用auto关键字推导常量类型时,需要特别注意:如果不使用const关键字,那么auto关键字不会保留常量性。
int x = 2;
const auto m = x; // const int
auto n = m; // int
auto& w = x; // const int&
如上,变量 m 的推导规则说明当表达式是一个引用类型时, auto 会把引用类型抛弃,直接推导成原始类型。
变量 n 的推导规则说明当表达式带有 const 时, auto 会把 const 属性抛弃,直接推导成 non-const 类型。
变量 w 的推导规则说明当 auto 与引用结合时, auto 的推导将保留const属性。
从而可以总结出,针对引用类型,当 auto 后不带引用,则推导结果将抛弃引用以及 const 属性( volatile 也一样);当 auto 与引用结合使用时,推导结果将保留原表达式的引用以及 const 属性( volatile 也一样)。
1.3.2 变量中的auto推导
auto 关键字提供了一种简洁的方式来声明变量,而不必明确指定其类型。这种类型推导的能力在某些情况下非常有用,但也可能带来一些意想不到的结果。
(1)初始化列表
如下,使用初始化列表来声明一个变量:
auto vals = {1, 2, 3, 4, 5, 6};
vals 的类型被推导为 std::initializer_list。
如果为初始化列表提供一个明确的类型时,情况会有所不同:
auto vals = std::vector<int>{1, 2, 3, 4, 5};
在这种情况下,vals 的类型被正确地推导为std::vector。
(2)带有表达式的推导
如下,使用初始化列表来声明一个变量:
auto vals = {1, 2, 3, 4, 5, 6};
vals 的类型被推导为std::initializer_list。
如果为初始化列表提供一个明确的类型时,情况会有所不同:
auto vals = std::vector<int>{1, 2, 3, 4, 5};
在这种情况下,vals 的类型被正确地推导为std::vector。
1.4 auto关键字的限制
(1)auto 关键字不能用于函数参数
void func(auto para){} //error
(2)auto 关键字不能作用于类的非静态成员变量
class A
{
public:
A(){}
~A(){}
private:
auto var1=0; //error
static auto var2=0; //OK,var2的类型为int
}
(3)auto 关键字不能定义数组
int a[10];
auto b = a; //OK, b -> int*
auto c = a; //error
1.5 auto关键字的常见使用场景
(1)带有模板类型的变量
以如下容器类的代码为例:
#include <map>
int main()
{
std::map<int,std::string> testMap;
//使用详细类型
std::map<int,std::string> it1 = testMap.begin();
//使用 auto
auto it2 = testMap.begin();
......
}
一般而言,模板类型会比较长,如果使用 auto 关键字,则会极大的简化代码,不只方便书写,而且阅读代码也会变得清爽。
更为重要的是,一旦类型变更,则只需修改定义处,其他使用auto都不用调整。
(2)无法确定变量类型的情况
如下代码所示:
class A
{
public:
A() {}
~A() {}
public:
std::string getVal()
{
return m_val;
}
private:
std::string m_val;
};
class B
{
public:
B() {}
~B() {}
public:
int getVal()
{
return m_val;
}
private:
int m_val;
};
template<class T>
class C
{
public:
C() {}
~C() {}
public:
//此处如果不使用 auto 关键字,则需要给该函数再增加一个模板参数,并且在调用时还需要手动指定返回类型
auto getVal()
{
return m_obj.getVal();
}
private:
T m_obj;
};
在上面代码中,getVal() 函数的返回值使用 auto 关键字,从而避免了大量复杂代码的书写。
2 decltype
2.1 引入
decltype 是"declare type"的缩写,译为"声明类型"。和 auto 的功能一样,该关键字用来在编译时期进行自动类型推导。引入 decltype 是因为 auto 并不适用于所有的自动类型推导场景,在某些特殊情况下 auto 用起来很不方便,甚至压根无法使用。也可以将 decltype 看作是 sizeof 运算符的另一种形式,因为两者都不会真正计算其参数,只充当一种编译期工具的角色。
2.2 decltype 关键字基本使用方式
decltype 的语法格式如下:
decltype(exp)
其中,exp表示一个表达式(expression)。如下为 decltype 的使用样例:
int x = 1;
decltype(1) a = 2; //a -> int
decltype(x) b = 2; //b -> int
decltype(a+b) c; //c -> int
const int& y = 1;
decltype(y) d = 2; //d -> const int&
const decltype(x)* e = &x; //e -> const int*
int func(int, char);
decltype (func(1, 'C')) f; //表达式为函数的时候,推导结果为返回值类型。注意函数需要带参,但不会真的运行函数
对于变量 c ,该变量可以不用初始化,这是和auto的不同之处。对于变量 d ,该变量会保留 y 的 const & 属性,这也是和auto的不同之处。变量 e 的推导表明 decltype 可以像 auto 一样,加上引用和指针,以及 cv 限定符。
2.3 decltype 关键字的推导规则
针对decltype 的语法格式 decltype(exp) ,其语法规则有如下三点:
(1)当 exp 是标识符、类访问表达式时, decltype(exp) 和 exp 的类型一致。
int x = 1;
decltype(x) a = 2; //a -> int (标识符)
const int& y = 1;
decltype(y) b = 2; //b -> const int& (标识符)
class A
{
public:
static int val1;
int val2;
}
decltype(A::val1) c = 2; //c -> int (类访问表达式)
A z;
decltype(z.val2) d = 2; //d -> int (类访问表达式)
(2)当 exp 是函数调用时, decltype(exp) 和 exp 的返回值类型一致。
int func1(void);
decltype (func()) a; //a -> int
int& func1(void);
decltype (func()) b; //b -> int&
(3)当 exp 是一个运算表达式,如果 exp 是一个左值,则 decltype(exp) 是 exp 类型的左值引用,否则其类型和 exp 的类型一致。
class A
{
public:
int val;
}
const A x = A();
decltype(x.val) a = 2; //a -> int
decltype((x.val)) b = 2; //b -> const int& (x.val是左值,括号表达式也是一个左值,因此推导后即为一个左值引用)
int y=0;
int z=1;
decltype(y+z) c = 2; //c -> int (y+z返回一个右值,因此推导后即为一个左值)
decltype(y+=z) d = 2; //d -> int & (y+=z返回一个左值,因此推导后即为一个左值引用)
2.4 decltype 与 auto 结合使用
有如下场景:
template <class A, class B>
decltype(a + b) add(A a, B b) //错误,a、b 在推导的时候还没有被定义
{
return a + b;
}
这段代码编译不过,因为a、b在参数列表中,C++的返回值是前置语法,这里先推导返回值类型是找不到a、b的定义。
将其修改为如下代码:
template <class A, class B>
auto add(A a, B b) -> decltype(a + b)
{
return a + b;
}
通过将返回类型修改为后置语法,即可成功编译通过。