現代的編程語言,無論是動態語言(JavaScript、Python 等),仍是靜態語言(Go、Rust 等),大都支持自動類型推導(type deduction)。c++
自動類型推導,通俗地講就是定義一個變量的時候不須要明確指定類型,而是讓編譯器根據上下文進行推導。編程
在 C++11 以前,模板(template)代碼就支持編譯器自動類型推導。C++11 很重要的一個特性就是增強了編譯器自動類型推導的能力,使之不限於模板 —— 與此相關的關鍵字有兩個 auto
和 decltype
。編程語言
咱們來看看 auto 關鍵字在 C++ 中的使用。 最簡單的用法,定義變量的時候不指定類型,經過初始化的值讓編譯器自動推導。函數
auto a; // 編譯不經過
auto b = 0; // b 是 int 類型
auto c = 0ull; // c 是 unsigned long long 類型
auto d = "Hello World"; // d 是 const char* 類型
auto e = std::string("Hello"); // e 是 std::string 類型
複製代碼
auto 和容器類型、迭代器一塊兒配合使用,能夠少打不少字,代碼也更簡潔、清晰。工具
std::vector<int> v(10, 1);
auto itr_begin = v.begin(); // std::vector<int>::iterator
auto itr_end = v.end(); // std::vector<int>::iterator
auto sz = v.size(); // std::vector<int>::size_type
複製代碼
若是不用自動類型推導,下面 v 的類型寫起來也很麻煩。若是 b 和 e 是自定義的迭代器,不必定能用 typename std::iterator_traits<Iter>::value_type
來得到類型。性能
template<typename Iter>
void Process(Iter b, Iter e) {
while (b != e) {
auto v = *b; // 若是不用自動類型推導,如何得到 *b 的類型
// typename std::iterator_traits<Iter>::value_type v = *b;
std::cout << v << std::endl;
++b;
}
}
複製代碼
類型推導能夠和 Lambda 表達式一塊兒愉快地使用。ui
auto Plus = [](int a, int b) { return a + b; };
複製代碼
也許有人會說,Lambda 表達式能夠用一個 std::function 對象來包裝。spa
std::function<int(int, int)> PlusFunc = [](int a, int b) { return a + b; };
複製代碼
可是這樣作有幾點很差:code
auto Plus = [](auto a, auto b) { return a + b; }; // std::function<T> 的類型無法寫了
std::cout << Plus(3, 4) << std::endl;
std::cout << Plus(3.14, 1.11) << std::endl;
std::cout << Plus(std::string("hello"), std::string("world")) << std::endl;
複製代碼
某些狀況下,自動類型推導還可讓你避免一些「坑」。好比:對象
std::unordered_map<std::string, int> m;
// ...
for (const std::pair<std::string, int>& pa : m) { // 你以爲有沒有問題?
// ...
}
複製代碼
看得出上面這段代碼有什麼問題嗎?
上面的代碼會致使複製整個 unordered_map。由於 std::unordered_map<Key, T>::value_type 的類型是 std::pair<const Key, T>。正確的寫法應該是:
for (const std::pair<const std::string, Foo>& pa : m) {
// ...
}
複製代碼
用自動類型推導能夠簡單避免這個坑:
for (const auto& pa : m) {
// ...
}
複製代碼
固然,用自動類型推導的時候,也可能引入一些坑。好比:
std::vector<bool> v2;
v2.push_back(true);
v2.push_back(false);
auto b2 = v2[0]; // b2 是什麼類型?
複製代碼
由於 std::vector 的特殊實現緣由,變量 b2 不是一個 bool 類型,而是一個自定義的類。(不管你是否使用自動類型推導,都儘量不要使用 std::vector。)
decltype 的做用是,告訴你一個表達式/變量/常量是什麼類型。好比:
std::cout << typeid(decltype(1)).name() << std::endl; // 輸出 i,表示 int
float f;
std::cout << typeid(decltype(f)).name() << std::endl; // 輸出 f,表示 float
unsigned a = 1;
unsigned long long b = 2;
std::cout << typeid(decltype(a + b)).name() << std::endl; // 輸出 y,表示 unsigned long long
複製代碼
typeid(T).name() 在不一樣的編譯器下的輸出可能不同。本文在 Ubuntu 上使用 gcc 7.5 進行編譯。typeid(T).name() 的輸出能夠經過 c++filt 工具轉換成實際可讀的類型名稱。
相比 auto,decltype 用得少不少。 舉一個例子:
template<typename T, typename U>
??? Plus(T t, U u)
return t + u;
}
複製代碼
t + u 到底應該返回什麼類型?
Plus(1, 2); // 返回值類型應該是 int
Plus(1, 2.0); // 返回值類型應該是 double
複製代碼
使用 decltype 的 trailing return type 來解決這個問題:
template<typename T, typename U>
auto Plus(T t, U u) -> decltype(t + u) {
return t + u;
}
複製代碼
C++ 14 進行了增強,能夠省掉這條尾巴。
template<typename T, typename U>
auto Plus(T t, U u) {
return t + u;
}
複製代碼
若是函數有多個 return 語句,須要保證它們返回的類型都是同樣的才能成功編譯。
// error: inconsistent deduction for auto return type: ‘int’ and then ‘double’
auto f(int i) {
if (i == 1) {
return 1;
} else {
return 2.0;
}
}
複製代碼
使用 auto 須要本身手動說明是值類型仍是引用類型。C++14 引入 decltype(auto) 來自動推導精確類型——其實 decltype(auto) 算是 decltype(expr) 的一個語法糖。
std::vector<std::string> v{"C++98", "C++03", "C++11",
"C++14", "C++17", "C++20"};
// v[0] 的返回值類型是 std::string&,可是 a 是 std::string
auto a = v[0];
// a 是 std::string&
auto& b = v[0];
// C++11,咱們能夠這樣肯定精確類型,c 是 std::string&
// 可是,若是 v[0] 變成一個複雜的表達式,代碼寫出來可能很難看懂
decltype(v[0]) c = v[0];
// C++14 引入了 decltype(auto),能夠自動推導出精確類型。d 是 std::string&
decltype(auto) d = v[0];
複製代碼