C++ Learning

第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 という既製の型があります。

▶ min_max 関数を試す

2 つの整数を入れると、min と max を 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";
first
10
second
"banana"

比較

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}
q
3
r
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;
<0>
1
<1>
"Alice"
<2>
98.5

構造化束縛と組み合わせると 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 のメンバは firstsecond。順に 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)。
この記事をシェア