第12回 std::pair / std::tuple と構造化束縛
2 つの値をまとめて持ちたい/返したいときに使うのが std::pair。3 つ以上なら std::tuple。わざわざ構造体を定義するほどでもない「一時的な組」を作るのに便利です。C++17 の構造化束縛 auto [a, b] = ... と組み合わせると、関数から複数の戻り値を受け取るコードがぐっと読みやすくなります。
このページで押さえること
✅ 最低限ここだけ覚える
std::pair<T1, T2> は 2 値の組
p.first / p.second でアクセス
auto [a, b] = p; で分解して受け取る(C++17)
- 関数から 2 値返すのに
std::pair
⭐ 余裕があれば読む
std::tuple で 3 値以上
std::get<0>(t) でアクセス
std::make_pair / std::make_tuple
- 構造化束縛は pair / tuple / 配列 / struct でも使える
1. まず触ってみる ― 2 つの値を一緒に扱う
「ある関数から2 つの値を一緒に返したい」場面はよくあります。たとえば「最小値と最大値を一度に返す」「商と余りを両方返す」など。C ではポインタ引数で無理やりやっていましたが、C++ にはstd::pair という既製の型があります。
まずは 3 行だけ
first_pair.cppC++ 最小例
#include <utility> // pair はここ(iostream や vector に入っている場合も)
std::pair<int, std::string> p{42, "answer"};
std::cout << p.first << " " << p.second;
// 出力: 42 answer
覚える 3 つ:
std::pair<型1, 型2> p{値1, 値2}; で作る
p.first が 1 つ目、p.second が 2 つ目
- C++17 なら
auto [a, b] = p; で分解して受け取れる(§4 で)
よくある素朴な疑問
Q. struct を自分で作るのと何が違う?
→ 自作の struct は「Point{x, y} のように意味のある名前」が付けられる利点があります。pair は名前がない(first/second だけ)ので使い捨ての組に向いています。意味が明確な 2 値なら struct、ざっくり組みたいだけなら pair。
Q. 3 つ以上まとめたいときは?
→ std::tuple<T1, T2, T3, ...> を使います。§5 で扱います。
Q. std::map の要素が pair って聞くけど?
→ はい。std::map<K, V> の要素は std::pair<const K, V>。だから map をループで回すと p.first(キー)と p.second(値)でアクセスします。詳しくは第 17 回「map」で。
2. std::pair の基本
作り方 3 パターン
作り方C++
// ① 明示的に型を指定
std::pair<int, std::string> p1{1, "apple"};
// ② make_pair で型を推論させる
auto p2 = std::make_pair(1, "apple");
// ③ C++17 以降: CTAD で型を省略
std::pair p3{1, std::string{"apple"}};
読み書き.first / .second
std::pair<int, std::string> p{10, "banana"};
int& a = p.first; // 10
std::string& b = p.second; // "banana"
// 書き換えも普通にできる
p.first = 99;
p.second = "cherry";
比較
pair どうしは == / < 等で比較できます。first を優先して、同じなら second で比較、という辞書順。
比較辞書順
std::pair<int, int> a{1, 2};
std::pair<int, int> b{1, 3};
a == b; // false
a < b; // true (first 同じ → second で比較 → 2 < 3)
// sort で辞書順ソート
std::vector<std::pair<int, int>> v = {{3,1}, {1,2}, {2,5}};
std::sort(v.begin(), v.end());
// v = {{1,2}, {2,5}, {3,1}}
「first でソート、first 同じなら second でソート」が自動で行われます。競技プログラミングでスコアつきデータを並べるときの定石。
3. 関数から 2 値を返す
C では「複数の値を返す」ために、ポインタ引数や構造体を使っていました。C++ では std::pair で返す→呼び出し側で分解、が定番です。
C の書き方出力引数
void min_max(int a, int b,
int* mn, int* mx) {
*mn = a < b ? a : b;
*mx = a > b ? a : b;
}
int mn, mx;
min_max(7, 3, &mn, &mx);
// 変数を事前に用意する必要あり
C++ の書き方pair で返す
std::pair<int, int> min_max(int a, int b) {
return {a < b ? a : b, a > b ? a : b};
}
auto p = min_max(7, 3);
// p.first = 3, p.second = 7
ポイント:
- 関数内で
return {値1, 値2}; と書けば自動で pair になる
- 受け取る側は
auto p = ... で OK
p.first / p.second の「意味が分からない」問題は、次の構造化束縛で解決
4. 構造化束縛(C++17)
ここが本回の目玉。C++17 から auto [a, b] = ...; という書き方で、pair を 2 つの変数に直接分解して受け取れるようになりました。
C++11 までfirst/second
auto p = min_max(7, 3);
int mn = p.first;
int mx = p.second;
// 読み手が p.first, p.second の意味を
// 毎回理解する必要がある
C++17~構造化束縛
auto [mn, mx] = min_max(7, 3);
// mn, mx という名前で直接使える
// 意図が一目で分かる
auto [q, r] = div_mod(17, 5); // 戻り値: pair{3, 2}
↙↘
左から順に first, second が取り出されて q, r に代入される
const 参照・参照でも使える
値コピー基本
auto [k, v] = some_pair; // コピー
const 参照重い型はこちら
const auto& [k, v] = some_pair; // コピーなし
auto& [k2, v2] = some_pair; // 書き換え可能
map のループで絶大な効果
C++11読みにくい
for (const auto& p : scores) {
std::cout << p.first << ": "
<< p.second << "\n";
}
C++17読みやすい
for (const auto& [name, score] : scores) {
std::cout << name << ": "
<< score << "\n";
}
⭐
ここまでで pair と構造化束縛は OK
残りは tuple(3 つ以上)と「自作 struct との使い分け」。気になる方だけどうぞ。
5. std::tuple ― 3 つ以上
3 つ以上の値をまとめたいときは std::tuple を使います。pair の一般化です。
作り方tuple
#include <tuple>
std::tuple<int, std::string, double> t{1, "Alice", 98.5};
// make_tuple でも
auto t2 = std::make_tuple(1, std::string{"Bob"}, 87.2);
アクセスget<N>
// .first/.second が使えない → std::get<N> を使う
int a = std::get<0>(t); // 1
std::string b = std::get<1>(t); // "Alice"
double c = std::get<2>(t); // 98.5
// 構造化束縛なら一発
auto [id, name, score] = t;
構造化束縛と組み合わせると tuple が一気に便利
std::get<0>(t) は書いていて冗長ですが、構造化束縛を使えば不要になります。tuple を使う現代的な書き方は、ほぼ常に構造化束縛とセット。
tuple の要素数と型が増えると読みにくくなる: 4〜5 個までが実用の上限。それ以上になるなら、意味のある名前を持つ struct を定義したほうが読みやすい(§6 で説明)。
6. pair / tuple vs 自作 struct
「2〜3 個の値をまとめて扱う」には pair / tuple の他に自作 struct という選択肢があります。使い分けを整理します。
pair / tuple組み捨て
auto [q, r] = div_mod(17, 5);
// 使い捨ての 2 値に便利
auto [mn, mx] = min_max(v);
// 関数内部の局所データ
自作 struct名前が意味を持つ
struct Student {
int id;
std::string name;
double score;
};
Student s{1, "Alice", 98.5};
std::cout << s.name << s.score;
使い分けの目安:
- 関数の中だけで使う一時的な組み合わせ → pair / tuple
- 複数の場所で使い回す/意味に名前を付けたい → 自作 struct
std::map のキー・値ペアが欲しい → pair(map の要素がすでに pair)
struct でも構造化束縛が使える: C++17 の構造化束縛は「public メンバだけを持つ集約構造体」にも使えます。auto [id, name, score] = s; のように分解可能。pair / tuple じゃないと使えない機能ではないので安心を。
確認クイズ
pair / tuple / 構造化束縛を 4 問で確認。
Q1. std::pair<int, std::string> p{1, "apple"}; のあと、"apple" を取り出す書き方は?
p.name
p[1]
p.second
p.get(1)
pair のメンバは first と second。順に 1 番目と 2 番目の要素を指します。[] は使えません。tuple の場合は std::get<0>(t) / std::get<1>(t) のように添字で取ります。
Q2. auto [a, b] = some_pair; という書き方が使える C++ のバージョンは?
C++98 から
C++11 から
C++17 から
C++20 から
構造化束縛(structured bindings)は C++17 で追加されました。本サイトは C++17 基準なので積極的に使っていきます。
Q3. 次のうち、std::pair<int,int> どうしを sort で昇順に並べたときの順序として正しいものは?
{3,1}, {1,2}, {2,5}, {1,1}
{3,1}, {1,2}, {2,5}, {1,1} (並び替えない)
{1,1}, {1,2}, {2,5}, {3,1}(second で昇順)
{1,1}, {1,2}, {2,5}, {3,1}(first 優先 → second)
{2,5}, {1,2}, {1,1}, {3,1}
pair の比較は辞書順(first を優先し、同じなら second)。first が 1 のものが先({1,1}, {1,2})、次に 2({2,5})、最後に 3({3,1})。first が同じ {1,1} と {1,2} は second で比較して {1,1} が先。
Q4. 3 つの値(int, string, double)をまとめて扱いたい。最もシンプルな書き方は?
std::pair<int, std::pair<std::string, double>>
std::vector に詰める
std::tuple<int, std::string, double>
std::pair を 3 つの変数として作る
どれも不可能
3 つ以上を組にするなら std::tuple が最適。①の pair 入れ子は動きますが読みにくい。vector は「同じ型を並べる」もので型が違うとき使えない。実務では「意味のある名前を付けたい」場合は自作 struct の方が良いこともあります(§6)。