現代 C++:自動類型推導

自動類型推導

現代的編程語言,無論是動態語言(JavaScript、Python 等),仍是靜態語言(Go、Rust 等),大都支持自動類型推導(type deduction)。c++

自動類型推導,通俗地講就是定義一個變量的時候不須要明確指定類型,而是讓編譯器根據上下文進行推導。編程

在 C++11 以前,模板(template)代碼就支持編譯器自動類型推導。C++11 很重要的一個特性就是增強了編譯器自動類型推導的能力,使之不限於模板 —— 與此相關的關鍵字有兩個 auto 和 decltype 。編程語言

auto

咱們來看看 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

  1. std::function 內部會涉及動態內存分配,性能上劣於自動類型推導的實現;
  2. 讓代碼看起來複雜很多;
  3. 對於泛型 Lambda 表達式,std::function 也無能爲力了。
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

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;
  }
}
複製代碼

decltype(auto)

使用 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];
複製代碼
相關文章
相關標籤/搜索