C++ tuple(STL tuple)模板用法詳解

2020-07-16 10:04:28
tuple<> 模板是 pair 模板的泛化,但允許定義 tuple 模板的範例,可以封裝不同型別的任意數量的物件,因此 tuple 範例可以有任意數量的模板型別引數。tuple 模板定義在 tuple 標頭檔案中。

tuple 這個術語也適用於很多其他的場景,例如資料庫,這裡一個 tuple 就是由一些型別的不同資料項組成的,這和 tuple 的概念相似。tuple 物件有很多用途。當需要將多個物件當作一個物件傳給函數時,tuple 型別是很有用的。

tuple 的操作

生成 tuple 物件的最簡單方式是使用定義在 tuple 標頭檔案中的輔助函數 make_tuple()。這個函數可以接受不同型別的任意個數的引數,返回的 tuple 的型別由引數的型別決定。例如:
auto my_tuple = std::make_tuple (Name{"Peter”,"Piper"},42,std::string{"914 626 7890"});
my_tuple 物件是 tuple<Name,int,string> 型別,因為模板型別引數是由 make_tuple() 函數的引數推匯出來的。如果提供給 make_tuple() 的第三個引數是一個字串常數,my_tuple 的型別將是 tuple<Name,int,const*>,這和之前的不同。

tuple 物件的建構函式提供了可能會用到的每一種選擇。例如:
std::tuple<std::string, size_t> my_tl;//Default initialization
std:: tuple<Name, std::string> my_t2 {Name {"Andy", "Capp"},std::string{“Programmer”}};
std::tuple<Name,std::string> copy_my_t2{my_t2}; // Copy constructor
std::tuple<std::string, std::string> my_t3 {"this", "that"};
// Implicit conversion
tuple 中的物件由預設建構函式用預設值初始化。為 my_t2 呼叫的建構函式將引數移到 tuple 的元素中。下一條語句會呼叫拷貝建構函式來生成 tuple,在最後一個建構函式呼叫中,將引數隱式轉換為 string 型別並生成了一個 tuple 元素。

也可以用 pair 物件構造 tuple 物件,pair 可以是左值,也可以是右值。顯然,tuple 只能有兩個元素。下面有兩個範例:
auto the_pair = std::make_pair("these","those");
std::tuple<std::string, std::string> my_t4 {the_pair}; std::tuple<std::string, std::string> my_t5 {std::pair <std::string, std::string > { "this", "that"}};
第二條語句從 the_pair 生成了一個 tuple,它是一個左值。the_pair 的成員變數 first 和 second 可以隱式轉換為這個 tuple 中的元素的型別。最後一條語句從右值 pair 物件生成了一個 tuple。

可以用任何比較運算子來比較相同型別的 tuple 物件。tuple 物件中的元素是按照字典順序比較的。例如:
std::cout << std::boolalpha << (my_t4 < my_t5) << std::endl;
tuple 物件中的元素是依次比較的,第一個不同的元素決定了比較結果。my_t4 的第一個元素小於 my_t5 的第一個元素,因此比較結果為 true。如果是相等比較,任何一對不相等的對應元素都會使比較結果為 false。

tuple 物件的成員函數 swap() 可以將它的元素和引數交換。引數的型別必須和 tuple 物件的型別一致。例如:
my_t4.swap (my_t5);
通過呼叫成員函數 swap() 來交換 my_t4 和 my_t5 對應的元素。顯然,tuple 中所有元素的型別都必須是可交換的,tuple 標頭檔案中定義了一個全域性的 swap() 函數,它能夠以相同的方式交換兩個 tuple 物件的元素。

因為 tuple 是 pair 的泛化,所以它的工作方式不同。pair 的物件個數是固定的,因此它們有成員名。tuple 中的物件數目是不固定的,所以存取它們的機制必須能夠滿足這種情況。函數模板 get<>() 可以返回 tuple 中的一個元素。第一個模板型別引數是 size_t 型別值,它 是 tuple 中元素的索引,因此 0 會選擇 tuple 中的第一個元素,1 會選擇第二個元素,以此類推。get<>() 模板剩下的型別引數是和 tuple 的引數同樣推導的。下面是一個使用 get<>() 和索引來獲取元素的範例:
auto my_tuple = std::make_tuple (Name {"Peter","Piper"}, 42, std::string {"914 626 7890"});
std::cout << std::get<0>(my_tuple)<< "age = "<<std::get<1>(my_tuple)<< " tel: " << std::get<2>(my_tuple) << std::endl;
在輸出語句中第一次呼叫 get<>() 時返回了 my_tuple 中第一個元素的參照,它是一個 Name 物件,第二次呼叫 get<>() 時返回了下一個元素的參照,它是一個整數;第三次呼叫 get<>() 時返回了第三個元素的參照,它是一個 string 物件。因此輸出結果是:
Peter Piper age = 42 tel: 914 626 7890
也可以用基於型別的 get<>() 從 tuple 獲取元素,但要求 tuple 中只有一個這種型別的元素。例如:
auto my_tuple = std::make_tuple(Name{"Peter", "Piper"}, 42, std::string {"914 626 7890"});
std::cout << std::get<Name>(my_tuple)<<" age = " << std::get<int> (my_tuple)<< " tel: " <<std::get<std::string>(my_tuple) << std::endl;
如果 tuple 中包含的 get<>() 型別引數值的元素不止一個,程式碼就無法編譯通過。這裡 tuple 的全部 3 個成員為不同型別,所以可以正常使用。

全域性的 tie<>() 函數模板定義在 tuple 標頭檔案中,它提供了另一種存取 tuple 元素的方式。這個函數可以把 tuple 中的元素值轉換為可以繫結到 tie<>() 的左值集合。tie<>() 的模板型別引數是從函數引數中推導的。例如:
auto my_tuple = std::make_tuple(Name{"Peter","Piper"}, 42, std::string{"914 626 7890"});
Name name{};
size_t age{};
std::string phone{};
std::tie(name, age, phone) = my_tuple;
在最後一條語句中,賦值運算子的左運算元表示式會返回一個引數的 tuple 參照。因此,賦值運算子左右的運算元都是 tuple 物件,並且用 my_tuple 中的元素值來對 tie() 引數中的變數賦值。我們可能並不想儲存每一個元素的值。下面展示了如何只儲存 my_tuple 中 name 和 phone 的值:
std::tie(name, std::ignore,phone) = my_tuple;
ignore 定義在 tuple 中,它被用來標記 tie() 函數中要被忽略的值。tuple 中被忽略的元素的值將不會被記錄下來。在這個範例中只複製了第一個和第三個元素。

也可以用 tie() 函數來實現對類的資料成員的字典比較。例如,可以在 Name 類中實現 operator<() 函數:
bool Name::operator<(const Name& name) const
{
    return std::tie(second, first) < std::tie(name.second, name.first);
}
在這個函數體中,呼叫 tie() 得到的 tuple 物件的元素是按順序比較的。用 < 運算子來比較連續的元素對,出現的第一對不同值會決定比較的結果;這個表示式的比較結果就是不同元素的比較結果。如果全部元素都相等或等價,那麼結果為 false。