C++98/03中的auto
C++98/03中的auto存储标识符,用来标识临时变量。现已经废止:
// C++98/03
auto int x = 5;
// C++11开始已经废止
auto int x = 5; // error
C++11中的auto与decltype
auto
C++11将auto存储标识符的语义废止,改成了类型指示符,在变量声明中,表示用变量的初始化表达式来推导变量的类型。在函数声明中,起占位符作用,表示函数包含尾置返回类型(trailing-return-type)。
C++11引入auto以后,带来了很多好处和改进,例如:
简化过长的类型名:
// C++98/03 // 过长的类型名称不仅书写麻烦,还很容易忘记。 // 而且很多时候不需要知道具体的类型,像这个例子一样, // 只需要知道通过调用std::find()得到的是一个迭代器, // 并且通过这个迭代器可以进行比较,解引用等操作即可。 std::vector<std::string> vec; // ... std::vector<std::string>::iterator iter = std::find(vec.begin(), vec.end(), "something"); if (iter != vec.end()) { // ... } // C++11 std::vector<std::string> vec; // ... // 用auto声明变量,无需手写复杂的类型 auto iter = std::find(vec.begin(), vec.end(), "something"); if (iter != vec.end()) { // ... }
用auto确定模板参数的计算结果:
// C++98/03 // 无法确定模板参数的计算结果 template <typename T, typename U> void f(T t, U u) { ??? v = t + u; // v 应该是什么类型? } // C++11 // 通过auto声明变量,可以用任何表达式推导出变量的类型, // 包括模板参数所组成的表达式 template <typename T, typename U> void f(T t, U u) { auto v = t + u; // 自动推导 t+u 结果的类型 }
用auto和decltype推断函数模板的返回值类型(decltype见下文):
// C++98/03 // 无法确定函数模板的返回值类型 template <typename T, typename U> ??? f(T t, U u) // 返回值应该是什么类型? { return t + u; } // C++11 // 用auto告知编译器模板的返回类型需要自动推导 // 用decltype(expression)告知编译器返回类型根据expression推导 // -> decltype(expression) 这种表示法叫做尾置返回类型 // C++14对这种情况进行了改进,见下文 template <typename T, typename U> auto f(T t, U u) -> decltype(t + u) { return t + u; }
使用和注意事项
auto声明中初始化表达式可以是任何合法的表达式:
auto x = 12; // 常量 auto y = x + 3.4; // 算数表达式 std::vector<std::string> vec; auto iter = vec.begin(); // 函数调用表达式 auto f = [](const std::string &) { /*...*/ }; // lambda表达式
由于auto需要用初始化表达式推导变量类型,因此auto声明必须初始化:
auto f; // 错误!必须初始化
auto声明的变量不能出现在初始化表达式中:
int x = x; // ok but useless auto y = y; // error
auto类型指示符可以与其他类型指示符(const, volatile等)合用:
const auto x = 12; volatile auto y = 34; inline auto x = 5; // C++17 constexpr int getBufferSize() { return 1024; } constexpr auto buffer_size = getBufferSize();
声明符(declarator)可以指定该变量是指针、引用等类型:
int i = 34; auto &ri = i; // ri 是引用类型 auto *pi = &i; // pi 是指针类型
可以在声明中包含声明符列表(多个声明符),但是推导的类型必须一致:
int y = 12; auto x = 5, *py = &y, &ry = y; // ok,所有推导类型一致 float f = 3.14; auto r = 5, *pf = &f; // 错误!auto由 r = 5 推导为int, // 由 *pf = &f 推导为float,两者不一致
auto会将初始值列表推导为std::initializer_list:
auto x = {1, 2}; // x 的类型为 std::initializer_list<int> auto y{1.0, 2.0}; // y 的类型为 std::initializer_list<double> auto z{3}; // z 的类型为 std::initializer_list<int>
任何可以声明变量的地方都能使用auto:
// // 在条件语句的声明中使用auto // if (auto result = getResult()) { // ... } switch (auto x = result()) { // ... } // // 在循环语句的声明中使用auto // for (auto iter = vec.begin(); iter != vec.end(); ++iter) { // ... } // C++11 range for for (const auto &str : vec) { // ... } while (auto running = isRunning()) { // ... } // // 在类中声明静态数据常量时使用auto // class Foo { static const auto bar = 12; };
在new表达式中使用auto:
auto pi = new auto(5);
auto标识函数包含尾置返回类型:
template <typename T, typename U> auto f(T t, U u) -> decltype(t + u) { return t + u; }
注意用auto推导一个返回代理类的表达式时可能会有危险。例如
std::vector<bool>::operator[]
会返回一个代理类std::vector<bool>::reference
来表示std::vector<bool>
中某个索引下的bool引用:std::vector<bool> vec = { true, false }; vec[0] = false; // 将第一个元素的值改为false // 等价于: std::vector<bool>::reference &&r = vec[0]; // 返回一个代理对象,表示索引为0的bool值 r = false; // 通过代理对象改变索引为0的bool值,需要注意的是 // std::vector<bool>::reference保留了std::vector<bool>内部数据的引用 bool status = vec[0]; // 将第一个元素的值赋值给status // 等价于: std::vector<bool>::reference &&r = vec[0]; // 返回一个代理对象,表示索引为0的bool值 bool status = r; // std::vector<bool>::reference定义了operator bool以转换成bool值
如果
std::vector<bool>
是一个临时对象,则不可将operator[]
的返回值赋值给任何std::vector<bool>::reference
类型的变量,包括auto推导的std::vector<bool>::reference
类型的变量:std::vector<bool> getResults() { std::vector<bool> vec = { true, false }; return vec; } std::vector<bool>::reference r = getResults()[0]; // getResults()返回临时std::vector<bool>对象, // getResults()[0]返回std::vector<bool>::reference对象,该对象 // 保留了临时std::vector<bool>对象内部数据的引用, // 赋值给r之后,r同样保留了临时std::vector<bool>对象内部数据的引用, // 执行完该语句之后临时std::vector<bool>对象被销毁, // r引用了无效的数据 bool status = r; // 未定义行为,r内部引用的数据已经无效 // 等价于: auto r = getResults()[0]; // auto 推导为 std::vector<bool>::reference bool status = r; // 未定义行为,r内部引用的数据已经无效
解决方法是隐式或显式转型,从而抛弃代理对象:
bool status = getResults()[0]; // 将std::vector<bool>::reference隐式转型为bool auto r = static_cast<bool>(getResults()[0]); // 显式转型
类型推导规则
类型标识符是指针或引用,但不是forwarding reference(universal reference by Scott Meyers)。则:忽略初始化表达式引用符号(&),再进行模式匹配:
const int& x = 12; // 忽略&再匹配 auto& a = x; // auto deduced to const int, a is const int& int y = 13; const auto& b = y; // auto deduced to int, b is const int& const int* z = nullptr; auto* c = z; // auto deduced to const int, c is const int* const auto* d = z; // auto deduced to int, d is const int*
类型标识符是forwarding reference。则:如果初始化表达式是左值,则匹配为左值引用;如果初始化表达式是右值,则匹配为右值引用:
int x = 12; auto&& a = x; // auto deduced to int&, a is int& auto&& a2 = std::move(x); // auto deduced to int&&, a2 is int&&
类型标识符既不是指针也不是引用。则:如果初始化表达式为引用,则忽略引用符号(&),忽略后,继续忽略顶级(top-level)const和volatile,再进行模式匹配:
const int& x = 12; // 忽略&,然后忽略顶级const auto a = x; // auto deduced to int, a is int const int *const px = &x; // 忽略顶级const const auto b = px; // auto deduced to int*, b is const int*
decltype
C++11引入的decltype用来推导表达式的类型。在变量声明中,可以用这个推导出的类型的作为变量的类型,而不必用该表达式作为变量的初始值。在函数(尤其是函数模板)声明中,可以用这个推导出的类型作为返回值。
用decltype声明变量:
decltype(foo()) bar; // bar的类型为foo()的返回类型 // 在推断foo()返回类型时,不对foo()求值
decltype用在尾置返回类型中:
template <typename T, typename U> auto f(T t, U u) -> decltype(t + u) // 函数返回值为decltype(t + u) { // auto为占位符,表示类型在参数列表后面 return t + u; }
使用和注意事项
decltype的类型推导规则和auto不同,decltype在类型推导时完全复制表达式的类型:
const int &x = 5; decltype(x) y = 6; // y 的类型为 const int & std::vector<int> w; decltype(w) vec; // vec 的类型为 std::vector<int>
decltype应用于产生左值的表达式则推导的结果为引用:
int x = 5; decltype(x) y = 6; // y 的类型是 int decltype(*px) rx = x; // *px是产生左值的表达式,rx 类型为 int& decltype((x)) z = x; // (x)是产生左值的表达式,因此 z 的类型为 int&
声明函数模板时,用于推导由参数类型决定的返回值类型:
template <typename T, typename U> auto f(T t, U u) -> decltype(t + u) { return t + u; }
C++14中的auto与decltype
C++14丰富了auto与decltype的用法:
用auto声明函数返回类型:
template <typename T, typename U> auto sum(T x, U y) // 根据return后面的表达式自动推断返回类型 { // 注意这里是以auto的类型推导规则来推断返回类型 return x + y; }
auto可用于lambda表达式的参数:
// C++11 std::vector<std::map<std::string, std::string>> vec; // 必须指明lambda表达式参数类型 auto f = [](const std::vector<std::map<std::string, std::string>> &vec){ /*...*/ }; f(vec); // C++14 std::vector<std::map<std::string, std::string>> vec; // 用auto推导参数类型 auto f = [](const auto &vec){ /*...*/ }; f(vec);
用decltype(auto)声明函数返回类型:
// C++11 template <typename T, typename U> auto sum(T x, U y) -> decltype(x + y) // 尾置返回类型 { return x + y; } // C++14 template <typename T, typename U> decltype(auto) sum(T x, U y) // 根据return后面的表达式自动推断返回类型 { // 注意这里是以decltype的类型推导规则来推断返回类型 return x + y; }
C++17中的auto与decltype
C++17明确了用初始值列表初始化auto变量的语义:
// C++11 auto x = {1, 2}; // x 的类型为 std::initializer_list<int> auto y{1.0, 2.0}; // y 的类型为 std::initializer_list<double> auto z{3}; // z 的类型为 std::initializer_list<int> // C++17 auto x = {1, 2}; // x 的类型为 std::initializer_list<int> auto y{1.0, 2.0}; // error! 直接列表初始化时列表中只能有一个值 auto z{3}; // 直接列表初始化,z 的类型为int
C++17可以在if和switch中声明变量,因此在if和switch里也可以使用auto:
if (auto iter = vec.begin(); iter != vec.end()) // c++17 { // ... } switch (auto status = getResponse(); status.code) // c++17 { // ... }
在结构化绑定(Structured binding)声明时使用auto:
struct S { int x1 : 2; volatile double y1; }; S f(); const auto [ x, y ] = f();