何爲换“心”
auto关键字是C++98就引入的关键字,C++98标准中,auto声明的变量为自动变量,就是说他具备自动生命周期。由于C++98规定函数代码段中声明的变量默认是auto变量,经常大家在声明变量时直接忽略auto。所以可以看出C++98中auto关键字是一个可有可无的关键字,无任何实质功能。但是从C++11开始,auto从原来可有可无转变成具备实质功能的C++关键字。这就是所谓的auto换“心”。
C++11标准委员会赋予了auto两个能力:第一,变量定义时可根据初始化表达式自动类型推导的能力;第二,声明函数返回值的占位符,允许函数后置返回类型。
C++14标准,C++17标准和C++17标准都分别对auto进行了升级扩展;C++14对auto函数占位符进行了简化,lambda形参声明允许使用auto;C++17将auto引入到非类型模板的声明和结构化绑定。C++20允许函数形参声明使用auto。
auto的标准演进
C++11
C++11标准委员会为auto引入了两个能力:第一,变量定义时可根据初始化表达式自动类型推导的能力;第二,声明函数返回值的占位符,允许函数后置返回类型。例如:
auto value = 1; // 推导为int
auto name = "liuguang"; // 推导为const char *
auto min(int a, int b) -> int // auto为int的占位符,这种返回类型后置是C语言早期的一种函数声明方式。
{
return (a < b) ? (a): (b);
}
由于auto会在编译过程中自动完成类型推导,因此在编码过程中要确保代码类型可推导。例如:
auto i; // 编译失败,编译期无法决策i的类型
此处之所以会编译错误,原因在于没有对i进行初始化,编译器无法确认i的具体类型。
C++14
C++14引入三个重要特性:第一,decltype(auto) ;第二,支持函数或 lambda 表达式的返回类型声明为auto,允许return语句的操作数推导返回auto类型。第三,允许lambda表达式使用auto形参声明。
auto x = 1 + 2;
decltype(auto) y = x; // y的类型是int,持有x的副本
decltype(auto) z = (x); // z的类型int&, z是x的引用
template<class T, class U>
auto add(T t, U u) // 返回类型是 operator+(T, U) 的类型
{
return t + u;
}
auto lambda = [](int x) {
return x + 3; };
auto lambda1 = [](auto a, auto b) {
return a + b;}; // lambda1的返回类型为decltype(a+b)
auto value = lambda1(1, 3.0); // value 被推导为double
C++17
从C++17标准开始,auto开始支持非类型模板的声明,auto非类型模板同样需要遵守非类型模板标准,即推导出的类型必须是可作为模板实参的。
template<auto n> // C++17 auto 形参声明
auto f() -> std::pair<decltype(n), decltype(n)> // auto 不能从花括号初始化器列表推导
{
return {
n, n};
}
f<5>(); // n为int
f<'a'>(); // n为char
f<1.0>(); // 编译失败double不能作为模板参数
除非类型模板声明,C++17标准还允许auto 说明符应用于结构化绑定声明。例如:
auto [v, w] = f<100>(); // 结构化绑定声明
C++20
函数形参声明允许使用auto关键字 ,这种函数声明可简化函数模板。
void f1(auto); // 与 template<class T> void f(T) 相同
auto min(auto a, auto b)
{
return (a < b) ? (a) : (b);
}
auto val1 = min(2, 5.0); // val1推导为double
auto val2 = min(2.0, 5.0);// val2推导为double
auto val3 = min(2.0, 5); // val3推导为double
auto推导规则
使用auto占位符声明变量必须初始化变量,由于初始化表达式存在多种形式,不同形式的表达式auto的推导规则也不尽相同,此部分主要介绍auto的推导规则。
按值初始化忽略cv限定符
在auto声明变量时,如果没有使用引用也没有使用指针。那么编译期推导时会忽略cv限定符。但是如果auto声明时使用了指针或引用,那么cv限定符不会忽略。例如:
const int i = 12;
auto j = i; // j会被推导为int
auto &k = i; // k声明时使用&,auto会被推导为const int,k类型为const int&
auto *x = &i; // x声明使用指针* auto会被推导为const int, x类型为const int*
const auto y = i; // auto 推导为int, y类型为const int
按引用初始化忽略引用属性
如果使用一个引用对象去初始化auto类型对象,那么原对象的引用属性会被忽略。例如:
const int i = 12;
auto &k = i; // k声明时使用&,auto会被推导为const int,k类型为const int&
auto j = k; // j 会推导为int 因为此处k的引用属性将会被忽略
auto与&&
在此需要特别说明的是auto与&&结合时,&&是万能引用而非右值引用。
如果初始化表达式为左值,根据&&引用折叠原理auto会决策为引用,如果初始化表达式为右值,auto会推导为表达式字面值类型。
int i = 100;
auto&& j = i; // 引用折叠,auto&& 推导为int&, 所以j的类型为int&
auto&& k = 200; // 引用折叠,auto&& 推导为int&&, 所以k的类型为int&&
auto同数组和函数
auto与数组结合,数组会退化为数组指针,auto与函数结合,函数也会退化为函数指针。
int array[100] = {
0};
auto i = array; // i推导为int*
auto min(int a, int b) -> int
{
return (a < b) ? (a): (b);
}
auto fn = min; // fn 推导的类型为int (*)(int, int)
&&与三目运算符
&&与三目运算符结合进行类型推导,编译期总是使用数据范围更大的类型。例如:
auto min = (true) ? 100, 2.3; // min会被推导为double,因为double比int类型范围更大。
auto与多变量
如果一个auto关键字声明多个变量,那么auto将会使用左结合性,也就是就近结合类型推导。此时只有满足声明类型统一才可以通过编译。否则会提示编译失败。
int value = 8;
auto *pValue = &value, n = 10; // auto 推导为int, n也为int,可以编译通过
auto *pValue = &value, m = 10.0; // auto 推导为int, m也为int,m不能赋值10.0
auto与成员声明初始化
虽然C++11支持类成员变量声明初始化,但是auto却无法应用于类非静态成员初始化。仅可以应用于const static 成员变量。
struct Person
{
auto age = 15; // 编译失败,不允许这么声明
};
struct Person
{
static const auto age = 15; // C++11可以编译通过
};
在C++17标准,进一步放松限制,允许static auto成员变量声明初始化。
struct Person
{
static auto age = 15; // C++17可以编译通过
};
auto与列表初始化
C++11标准下,直接列表初始化和等号赋值列表初始化,均推导为std::initializer_list。
auto d = {
1, 2}; // d的类型是 std::initializer_list<int>
auto n = {
5}; // n 的类型是 std::initializer_list<int>
auto e{
1, 2}; // C++11标准,e的类型是std::initializer_list<int>
auto m{
5}; // C++11标准,m的类型是std::initializer_list<int>
C++17标准下,等号赋值列表初始化推导为std::initializer_list,直接赋值初始化仅允许单个元素。
auto d = {
1, 2}; // d的类型是 std::initializer_list<int>
auto n = {
5}; // n 的类型是 std::initializer_list<int>
auto e{
1, 2}; // C++17标准,编译报错误
auto m{
5}; // C++11标准,m的类型是int
两份代码一模一样,读者可对比C++11和C++17 auto列表初始化类型推导差异。
decltype(auto)
decltype(auto) 是C++14标准引入的占位约束。意义在于通过decltype推导表达式规则来推导auto。
auto a = 1 + 2; // a 的类型是 int
decltype(auto) c1 = a; // c1 的类型是 int,保有 a 的副本
decltype(auto) c2 = (a); // c2 的类型是 int&,它是 a 的别名
C++20中decltype(auto)增加的类型约束功能。关于类型约束笔者会在decltype相关的博客中详细论述。这里就不赘述,仅给出一个例子。
// 在它调用的函数返回引用的情况下
// 函数调用的完美转发必须用 decltype(auto)
template<class F, class... Args>
decltype(auto) perfectForward(F fun, Args&&... args)
{
return fun(std::forward<Args>(args)...);
}
auto使用场景
在编码过程中,关于何时使用auto,不同的开发人员会有不同的见解和看法。但是这里面依然存在某些规律可寻。这里为大家介绍几种大家广泛认可的使用场景。
- 明确知晓初始化类型时可使用auto,例如:
auto i = 2;
- 复杂的类型,例如:lambda表达式,bind以及STL迭代器。lambda和bind类型有时候我们自己都很难描述,而STL迭代器则是因为类型太长而影响代码阅读。
std::unordered_multimap<std::string, int> unorderedMap;
unorderedMap.insert(std::make_pair("li", 10));
unorderedMap.insert(std::make_pair("liu", 50));
unorderedMap.insert(std::make_pair("wu", 49));
std::unordered_multimap<std::string, int>::iterator iter = unorderedMap.find("li");
// auto iter = unorderedMap.find("li"); 使用auto iter定义可简化为auto iter;
if (iter != unorderedMap.end()) //查找成功
{
std::cout << "name: " << iter->first << "age: " << iter->second;
}
// 你可以准确的描述fnIsEven的具体类型吗? 在此只有auto最简单。
auto fnIsEven = [](int value) -> bool {
return (0 == value % 2);
};
总结
本文详细为大家介绍了auto关键字的演进过程以及auto关键字的类型推导规则。希望对你有帮助。