auto
的作用
auto
是我在编码中经常使用到的C++11
新特性之一,主要用于变量的自动类型推导,如auto num=3
,则推导出num
的类型为int32_t
auto
的优势
相较于原始的显式类型去声明变量类型,auto
的优势有以下几点:
-
auto
变量一定要被初始化,可以避免变量忘记初始化// auto i; // error: declaration of 'auto i' has no initializer auot i=1; // ok
-
可简化变量/对象的声明
std::vector<int32_t> vec(1,2,3,4); auto iter=vec.begin(); // auto自动推导为std::vector<int32_t>::iterator
-
在某些场合无法判断出类型时,可用auto自动推导(如
lambda
表达式)auto func = [](int32_t a, int32_t b) { return a + b; }
-
可自适应类型,避免类型转换出现可能的类型错误
int64_t getFileSize(const std::string& filePath) { std::fstream fs(filePath,std::ios::in|std::ios::binary); if (!fs.is_open()) { return 0; } fs.seekg(0,std::ios::end); int64_t len=fs.tellg(); fs.close(); return len; } int main() { // int32_t len=getFileSize("./main.exe"); // 可能发生整形溢出 auto len=getFileSize("./main.exe"); // 自动推导为int64_t类型 }
auto
类型推断
auto
类型推断的逻辑和模板类型推断是一致的,所以我们可以先了解下模板类型推断规则,然后再去推广到auto
类型推断
模板类型推断,我们可以以一段伪代码为例
// 模板函数
template<typename T>
void f(ParamType param);
// 模板函数调用
f(expr);
在编译过程中编译器会使用expr
推断两种类型:一个T的类型,一个是ParamType
。而这两种类型往往是不一样的,因为ParamType
通常会包含修饰符,比如const
或者引用,所以这里模板函数推断过程可以分为以下几种情况
-
ParamType
是指针或普通引用类型,模板函数如下:template<typename T> void f(T& param);
推导的过程如下
-
若
expr
具有引用类型,先将引用部分忽略 -
对
expr
的类型和ParamType
的类型进行匹配,来决定T
的类型int32_t x=27; const int32_t cx=x; const int32_t& rx=x; f(x); // void f<int32_t>(int32_t ¶m),T的类型为int32_t,param的类型为int32_t& f(cx); // void f<const int32_t>(const int32_t ¶m),T的类型为const int32_t,param的类型为const int32_t& f(rx); // void f<const int32_t>(const int32_t ¶m),T的类型为const int32_t,param的类型为const int32_t&
-
-
ParamType
是一个万能引用(Universal Reference
),模板函数如下:template<typename T> void f(T&& param);
推导的过程如下
-
如果
expr
是个左值,T
和ParamType
都会被推导为左值引用。首先,这是模板类型推断中唯一将T推断为引用的情况;其次,虽然ParamType
的声明使用右值引用语法,但它最终却被推断成左值引用。 -
如果
expr
是个右值,参考ParamType
是指针或者普通引用类型情况,先将引用部分忽略,然后expr
和ParamTyoe
的类型进行匹配,来决定T
的类型int32_t x=27; const int32_t cx=x; const int32_t& rx=x; f(x); // void f<int32_t &>(int32_t ¶m),T的类型为int32_t&,param的类型为int32_t& f(cx); // void f<const int32_t &>(const int32_t ¶m),T的类型为const int32_t&,param的类型为const int32_t& f(rx); // void f<const int32_t &>(const int32_t ¶m),T的类型为const int32_t&,param的类型为const int32_t& f(27); // void f<int>(int &¶m),T的类型为int32_t,param的类型为int32_t&&
-
-
ParamType
既不是指针也不是引用。这种情况也就是所谓的按值传递,模板函数如下:template<typename T> void f(T param);
无论传入的是什么
expr
,param
也只是一份副本。推导的过程如下:-
若
expr
具有引用类型,则忽略其引用部分 -
若
expr
携带const
限定符,也忽略它int32_t x=27; const int32_t cx=x; const int32_t& rx=x; f(x); // void f<int32_t>(int32_t param),T的类型为int32_t,param的类型为int32_t f(cx); // void f<int32_t>(int32_t param),T的类型为int32_t,param的类型为int32_t f(rx); // void f<int32_t>(int32_t param),T的类型为int32_t,param的类型为int32_t f(27); // void f<int32_t>(int32_t param),T的类型为int32_t,param的类型为int32_t
-
-
数组参数属于模板类型推断的一种特殊情况
-
按值传递时由于数组退化为指针的规则,数组参数会被当做指针参数来处理,所以数组会被推断为一个指针类型
template<typename T> void f(T param) { } int main() { const char szName[]="Mark Lin"; // szName的类型为const char szName[9] f(szName); // void f<const char *>(const char *param),T的类型为const char*,param的类型为const char* }
-
ParamType
是指针或普通引用类型,T
的类型会被推导为数组类型,ParamType
则为数组引用template<typename T> void f(T& param) { } int main() { const char szName[]="Mark Lin"; // szName的类型为const char szName[9] f(szName); // void f<const char [9]>(const char (¶m)[9]),T的类型为const char[9],param的类型为const char (&)[9]) }
使用这种声明有一个妙用。我们可以创建一个模板来推断出数组中包含的元素数量:
template<typename T, std::size_t N> constexpr std::size_t arraySize(T (&)[N]) noexcept { return N; }
-
auto
的类型推断机制是和模板类型推断是一致的,auto关键字扮演的是模板类型推断中T的角色,而类型说明符扮演的是ParamType
的角色
-
auto
修饰为指针或普通引用类型//类比如下模板函数 //template<typename T> //void f(T& param) int32_t num=27; auto& x=num; // auto被推断为int32_t,x的类型为int32_t& auto& lx=x; // auto被推断为int32_t,lx的类型为int32_t&
-
auto
修饰是一个万能引用(Universal Reference
)//类比如下模板函数 //template<typename T> //void f(T&& param) int32_t num=27; auto&& x=num; // auto被推断为int32_t&,rx的类型为int32_t& auto&& lx=x; // auto被推断为int32_t&,rx的类型为int32_t& auto&& rx=27; // auto被推断为int32_t,rx的类型为int32_t&&
-
ParamType
既不是指针也不是引用//类比如下模板函数 //template<typename T> //void f(T param) auto x = 27; // auto被推断为int32_t,x的类型为int32_t const auto cx = x; // auto被推断为int32_t,cx的类型为const int32_t
-
数组参数和函数指针的情况
const char name[] = "R. N. Briggs"; auto arr1 = name; // auto被推断为const char*,arr1的类型为const char* auto& arr2 = name; // auto被推断为const char[13],arr2 的类型为const char (&)[13] void someFunc(int, double); auto func1 = someFunc; // func1的 类型为 void (*)(int, double) auto& func2 = someFunc; // func2的类型为 void (&)(int, double)
-
大多数场景下auto推断规则和模板参数推断规则相通,有一种特殊情况,就是统一初始化式。可以看下下述3个例子的区别
// ok auto x = { 11, 23, 9 }; // x的类型为 std::initializer_list<int> // error template<typename T> // 和auto x等同的模板类型推断 void f(T param); f({ 11, 23, 9 }); // 错误!这里不能推断T的类型。 // ok template<typename T> void f(std::initializer_list<T> initList); f({ 11, 23, 9 }); // T被推导成int,initList的 // 类型是std::initializer_list<int>
参考资料
[^1]https://www.oreilly.com/library/view/effective-modern-c/9781491908419/ch01.html