HOWTO · C++
如何在 C++ 中建立、存取及列印 std::tuple
了解如何建立異質的 std::tuple、存取與更新其元素,並列印不同大小的元組。
本頁內容
當單一值需要組合固定數量、型別各異的欄位時,例如姓名、識別碼與分數,可以使用 std::tuple。本指南將說明如何使用 std::make_tuple 建立這類值、使用 std::get 讀取及更新元素,以及列印不同大小元組中的所有元素。元組元素的位置屬於其型別的一部分,因此適合用來表示欄位已知的小型資料記錄,不適合用作執行期間會增長的集合。
std::tuple 儲存什麼,以及何時使用
std::tuple 是表示固定大小值序列的類別範本。每個位置都可以有不同型別,而且所有位置的型別與順序在編譯時就已確定。例如,元組可以同時存放字串、整數與浮點數。它的型別是 std::tuple;它不是函式或命令。
元組的位置沒有名稱,因此適合用於簡短的結果或區域性資料分組;若欄位需要在大型程式中始終以固定名稱表示,元組就較不容易表達其意義。欄位名稱能讓資料更容易理解時,請使用 struct;元素數量會變動時,請使用容器。
使用 std::make_tuple 建立元組
std::make_tuple 接受值並推導對應的元素型別,因此建立元組時不必在呼叫處寫出冗長的 std::tuple<...> 型別。元素會依照引數順序排列。使用 std::tuple 和 std::make_tuple 時,請納入 <tuple>;若要儲存 std::string 值,也請納入 <string>。下方完整程式中的 shortTuple 由 std::string、int 和 double 建立;std::make_tuple 會根據引數推導這些元素型別。值會依其型別複製或移動到元組中。這是建立值的方式,不是建立大小可動態變化序列的方法。
使用 std::get 讀取及更新元組元素
使用 std::get<I>(value) 存取位置 I 的元素;I 是從零開始、在編譯時決定的索引。三元素元組的有效位置是 0、1 和 2。下方完整程式會使用 std::get<1>(profile),將整數欄位讀入 originalId,接著再將相同運算式放在指定運算的左側,以更新該欄位。選取的位置必須有效,因此不能將只在執行時才計算出的索引傳給 std::get。
std::tuple 沒有方括號索引方式。若索引需要在執行時選定,請改用其他表示方式,例如元素同質的容器,或針對元組中已知的位置撰寫明確的分派邏輯。使用 std::get 讀取時會保留元素型別,因此編譯器可以依實際欄位型別檢查指定運算及函式呼叫。
使用可變參數範本列印所有元素
可變參數範本能接受元素數量不同的元組。下方範例需要 C++17 或更新版本,因為它使用摺疊運算式。程式以 std::index_sequence 產生編譯時的位置,再用摺疊運算式將各位置對應的值輸出至串流。輸出會加上括號,並以逗號分隔各值;每種元素型別都必須支援插入 std::ostream。程式會建立並更新一個元組,接著列印另外兩個大小不同的元組。輸出如下;其中的 Ada、Lin 和 C++ 依原例保留,並非翻譯後的標籤:
執行此範例:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
#include <iostream>
#include <string>
#include <tuple>
#include <utility>
using namespace std;
template <class Tuple, size_t... I>
void printTupleImpl(const Tuple& values, index_sequence<I...>) {
size_t printed = 0;
((cout << (printed++ ? ", " : "") << get<I>(values)), ...);
}
template <class... Values>
void printTuple(const tuple<Values...>& values) {
cout << "(";
printTupleImpl(values, index_sequence_for<Values...>{});
cout << ")\n";
}
int main() {
auto profile = make_tuple(string("Ada"), 7, 9.5);
const int originalId = get<1>(profile);
get<1>(profile) = originalId + 1;
auto shortTuple = make_tuple(string("Ada"), 7, 9.5);
auto longTuple = make_tuple(string("Lin"), 12, 8.25, string("C++"));
printTuple(shortTuple);
printTuple(longTuple);
}
(Ada, 7, 9.5)
(Lin, 12, 8.25, C++)
輔助函式會依照 std::index_sequence_for 提供的每個索引,展開一個 std::get<I> 運算式。逗號摺疊會依序處理這些運算式,因此同一個函式無須為每種長度分別撰寫多載,就能處理不同大小的元組。它也能處理空元組:摺疊運算不會插入任何值,函式仍會輸出括號。列印的值本身仍必須能輸出至串流;若元組包含自訂型別,就需要為該型別提供適當的串流插入多載。
如何選擇 std::tuple 或 std::pair
std::pair 恰好儲存兩個值,而 std::tuple 可以儲存任意固定數量的值,包括兩個。若資料自然由兩個相關欄位組成,例如鍵和值,優先使用 std::pair,因為它的型別能直接表達這種結構。若精簡的資料組包含三個以上型別不同的值,或介面刻意將多個值一併回傳,則選用 std::tuple。
若資料會在整個程式碼庫中廣泛使用,具名 struct 通常比這兩種型別更能傳達意圖。若只是暫時分組或用於簡潔的回傳值,std::tuple 可以省去另行定義型別。在這兩種情況下,都應讓欄位順序清楚,並使用 std::get 存取元組位置,讓讀者能辨認每個索引代表的值。