Effective Modern C++ 系列之 條款2: auto

yifanwu發表於2021-09-09

Effective Modern C++ 系列之 條款2: auto 型別推導

在理解模板型別推導規則後,那麼auto型別推導不是什麼問題了,除了一個奇妙的例外情況以外,auto型別推導就是模板型別推導.

template<typename T>
void f(ParamType param);

而一次呼叫形如:

f(expr);

在f呼叫中,編譯會根據expr來推導T和ParamType的型別.
當變數採用auto來宣告時, auto就扮演了模板中T的角色, 而變數的型別修辭詞則扮演的是ParamType的角色.
例如:

auto x = 27;        (1)  x的修飾詞就是auto自身 .
const auto cx = x;  (2)  cx的修飾詞 const auto .
const auto& rx = x; (3)  rx的修飾詞 const auto& .

在C++中auto型別推導和模板型別推導是一模一樣的(除了在一個例外情況下),可以利用模板型別推導規則模擬auto型別推導過程如下:

template<typename T>   //模擬auto型別推導過程.
void func_for_x(T param);

func_fox_x(27);   // 推導得出param的型別就是X的型別.

template<typename T>
void func_for_cx(const T param);

func_for_cx(x)  // 推導得出param的型別就是cX的型別.

template<typename T>
void func_for_rx(const T& param);

func_for_rx(rx);  //推導得出的param的型別就是rx的型別.

1. auto型別推導與模板型別推導一模一樣的規則.

採用auto進行變數宣告中,型別修飾詞取代ParamType,所以也存在三種情況:

1.1 型別修飾詞是指標或引用,但不是萬能引用.

auto x = 27 ;  
const auto cx = x ;
const auto& rx = x ;

1.2 型別修飾詞是萬能引用.

auto&& uref1 = x;    // x的型別是int, 且是左值,所以uref1的型別是int&。
auto&& uref2 = cx;   // cx的型別是const int, 且是左值. 所以uref2的型別是const int&.
auto&& uref3 = 27;   // 27的型別是int, 且是右值. 所以uref3的型別是int&&.

1.3 型別修飾詞既非指標也非引用.

在條款1中,陣列和函式名字如何在非引用修飾詞的前提下退化成指標,也同樣適合auto型別推導

const char name[] = "J. P. Briggs";  //name的型別是const char[13].
auto arr1 = name;   // arr1的型別是const char*.
auto& arr2 = name;  // arr2的型別const char(&)[13].

void someFunc(int, double);     //someFunc是個函式,型別是void(int, double).
auto func1 = someFunc;          //func1的型別是void(*)(int, double).
auto& func2 = someFunc;         //func2的型別是void(&)(int, double).    

2. auto型別推導與模板型別推導不同存在差異的情況.

auto宣告變數的初始化表示式使用{}時,推導所得的型別屬於std::initializer_list

auto x = {1,2,3,4,3.0} //錯誤,推導不出std::initializer_list<T>中T.

模板傳入一個{}的初始化表示式,型別推導就會失敗

auto x = {1,2,3}  //x的型別是std::initializer_list<int>

template<typename T>
void f(T param)   

f({1,2,3,4}); // 無法推導T的型別.

注意: auto 和 模板型別推導真正的唯一區別在於,auto會假定用大括號括起來的初始化表示式代表一個std::initializer_list,但模板型別推導卻不會.

在函式返回值或者lambda式的形象中使用auto,意思是使用模板型別推導而非auto型別推導.

auto createInitList {
    return {1,2,3,4};  //錯誤,無法為{1,2,3}完成型別推導.
}

std::vector<int> v;
...
auto resetv = [&v](const auto& newValue) { v = newValue }; //c++14
...

restv({1,2,3}); //錯誤!無法為{1,2,3}完成型別推導.

4. 總結

  • 在一般情況下,auto型別推導和模板型別推導是一模一樣的,但是auto型別推導會假定用大括號括起來的初始化表示式代表一個std::initializer_list,但是模板型別推導卻不會.
  • 在函式返回值或lambda式的形參中使用auto,意思是使用模板型別推導而非auto型別推導.

5. 驗證程式碼

#include <boost/type_index.hpp>
#include <vector>
#include <iostream>

using namespace std;
using namespace boost;
using boost::typeindex::type_id_with_cvr;

int main(int argc, char** argv) {
	auto&& x = 12;
	std::cout << "param =" << type_id_with_cvr<decltype(x)>().pretty_name() << std::endl;
	return 0;
}

本文學習《 Effective Modern C++ 》一書整理的學習筆記

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2157/viewspace-2825370/,如需轉載,請註明出處,否則將追究法律責任。

相關文章