M.Hiroi's Home Page

お気楽C++プログラミング超入門

応用編 : タプル (tuple)


Copyright (C) 2015-2023 Makoto Hiroi
All rights reserved.

はじめに

今回は C++11 で導入された「タプル (tuple)」というコンテナクラスを簡単に説明します。タプルは SML/NJ, OCaml, Haskell など多くの関数型言語でサポートされている immutable なデータ構造です。スクリプト言語では Python がタプルをサポートしています。

●タプルとは?

C++の場合、tuple は構造体 pair を拡張したデータ構造です。pair は 2 つの要素しか格納できませんが、tuple は複数の要素を格納できるクラスです。pair と同様に、要素のデータ型は異なっていてもかまいません。pair はメンバ変数 first, second の値を書き換えることができますが、tuple は要素の値を書き換えることはできません。ご注意くださいませ。

tuple を使用するときは、ヘッダファイル tuple をインクルードしてください。変数の宣言は次のように行います。

  1. tuple<データ型, ...> 変数名;
  2. tuple<データ型, ...> 変数名(値, ...);

tuple はテンプレートなので、< > の中に格納する要素のデータ型を指定してください。1 の場合、指定したデータ型のデフォルト値を格納したタプルが生成されます。普通は 2 のように、値を指定してタプルを生成するか、関数 make_tuple を使います。make_tuple はあとで説明します。

このほかにも、コピーコンストラクタやムーブコンストラクタ、pair からタプルを構築するコンストラクタなどが用意されています。詳細はC++のリファレンスマニュアルをお読みください。

要素は関数 get でアクセスすることができます。

get<位置>(タプル);

位置は配列と同じく先頭要素が 0 番目になります。範囲外の位置を指定するとコンパイルエラーになります。

簡単な使用例を示しましょう。

リスト : tuple の簡単な使用例 (sample2911.cpp)

#include <iostream>
#include <tuple>
using namespace std;

int main()
{
  tuple<int, double, string> a;
  cout << get<0>(a) << endl;
  cout << get<1>(a) << endl;
  cout << get<2>(a) << endl;
  tuple<int, double, string> b(1, 1.234, "foo");
  cout << get<0>(b) << endl;
  cout << get<1>(b) << endl;
  cout << get<2>(b) << endl;
  pair<string, int> p = {"bar", 20};
  tuple<string, int> c(p);
  cout << get<0>(c) << endl;
  cout << get<1>(c) << endl;
}

実行結果は次のようになります。

$ clang++ sample2911.cpp
$ ./a.out
0
0

1
1.234
foo
bar
20

変数 a は int, double, string を格納するタプルがセットされます。値はデフォルト値 (0, 0.0, "") になります。変数 b には 1, 1.234, foo を格納したタプルがセットされます。変数 c のタプルは pair<string, int> から生成されます。

●make_tuple によるタプルの生成

タプルは関数 make_tuple で生成することもできます。この場合、make_tuple の引数がタプルの要素にコピーされます。引数が右辺値であれば、ムーブセマンティクスが働きます。make_tuple を使うと、変数の宣言に auto を使うことができるので便利です。

簡単な使用例を示します。

リスト : make_tuple の使用例 (sample2912.cpp)

#include <iostream>
#include <tuple>
using namespace std;

class Foo {
  int x;
public:
  Foo() : x(0) {}
  Foo(int n) : x(n) {}
  Foo(const Foo& a) : x(a.x) { cout << "copy " << x << endl; }
  Foo(Foo&& a) : x(a.x) { cout << "move " << x << endl; }
  int get_x() const { return x; }
};

int main()
{
  auto a = make_tuple(1, 1.234, "foo");
  cout << get<0>(a) << endl;
  cout << get<1>(a) << endl;
  cout << get<2>(a) << endl;
  Foo b(10);
  auto c = make_tuple(b, Foo(20));
  cout << get<0>(c).get_x() << endl;
  cout << get<1>(c).get_x() << endl;
}
$ clang++ sample2912.cpp
$ ./a.out
1
1.234
foo
move 20
copy 10
10
20

変数 a, c の値は make_tuple の返り値なので、データ型は auto で宣言することができます。make_tuple に変数 b の値を渡すとコピーコンストラクタが呼び出され、右辺値 Foo(20) を渡すとムーブコンストラクタが呼び出されることがわかります。

●タプルにポインタや参照を格納する

tuple はポインタや参照も格納することもできます。次の例を見てください。

リスト : tuple にポインタや参照を格納する (sample2913.cpp)

#include <iostream>
#include <tuple>
using namespace std;

int main()
{
  int a = 1;
  double b = 1.2345;
  string c = "hello, world";
  tuple<int, double*, string&> d(1, &b, c);
  cout << get<0>(d) << endl;
  cout << *(get<1>(d)) << endl;
  cout << get<2>(d) << endl;
  *(get<1>(d)) = 12.345;
  get<2>(d) = "foo bar";
  cout << *(get<1>(d)) << endl;
  cout << get<2>(d) << endl;
}
$ clang++ sample2913.cpp
$ ./a.out
1
1.2345
hello, world
12.345
foo bar

ポインタや参照の場合、ポインタ先や参照先の変数の値を書き換えることができます。この場合、tuple に格納されている値は書き換わっていないことに注意してください。

tuple に参照を格納する場合は関数 tie を使うと便利です。tie は引数の参照を格納したタプルを生成して返します。簡単な例を示します。

リスト : 関数 tie の使用例 (sample2914.cpp)

#include <iostream>
#include <tuple>
using namespace std;

int main()
{
  int a = 1;
  double b = 1.2345;
  string c = "hello, world";
  auto d = tie(a, b, c);
  cout << get<0>(d) << endl;
  cout << get<1>(d) << endl;
  cout << get<2>(d) << endl;
  get<0>(d) *= 10;
  get<1>(d) *= 100;
  cout << get<0>(d) << endl;
  cout << get<1>(d) << endl;
}
$ clang++ sample2914.cpp
$ ./a.out
1
1.2345
hello, world
10
123.45

変数 d のデータ型は tuple<int&, double&, string&> になります。tie は変数 a, b, c の参照を格納したタプルを生成して返します。参照先の変数の値を書き換えることもできます。

●タプルの要素を取り出す

もう一つ tie には便利な使い方があって、tuple に格納されている値を変数に取り出すことができます。

auto t = tuple<データ型, ...>(値, ...);
tie(変数, ...) = t;

tie にタプル t を代入すると、t の要素を tie で指定した変数にセットすることができます。

簡単な使用例を示します。

リスト : tie の使用例 (sample2915.cpp)

#include <iostream>
#include <tuple>
using namespace std;

int main()
{
  int a;
  double b;
  string c;
  auto d = make_tuple(1, 1.2345, "foo");
  tie(a, b, c) = d;
  cout << a << endl;
  cout << b << endl;
  cout << c << endl;
}
$ clang++ sample2915.cpp
$ ./a.out
1
1.2345
foo

●タプルで多値を返す

一般に、関数の返り値はひとつしかありませんが、なかには複数の値を返すことができるプログラミング言語もあります。たとえば Lisp / Scheme では、この機能を「多値 (Multiple Values)」といい、複数の値を効率的に返すことができるようになっています。C++の場合、クラスや構造体を使って複数の値を返すことができますが、tuple と tie を使うともっと簡単に「多値」を実現することができます。

簡単な例を示しましょう。

リスト : tuple による多値の実装 (sample2916.cpp)

#include <iostream>
#include <vector>
#include <tuple>
using namespace std;

template<class T>
tuple<T, T> max_min(vector<T>& vec)
{
  T x = vec[0];
  T y = vec[0];
  for (int i = 0; i < vec.size(); i++) {
    if (x < vec[i]) x = vec[i];
    else if (y > vec[i]) y = vec[i];
  }
  return make_tuple(x, y);
}

int main()
{
  vector<int> a = {5, 6, 4, 7, 3, 8, 2, 9, 1};
  int x, y;
  tie(x, y) = max_min(a);
  cout << x << endl;
  cout << y << endl;
}
$ clang++ sample2916.cpp
$ ./a.out
9
1

関数 max_min は vector の中から最大値と最小値を求めて、それらをタプルに格納して返します。呼び出す側では、最大値と最小値を受け取る変数 x, y を用意して、tie(x, y) で指定すれば、max_min の返り値を変数 x, y にセットすることができます。

受け取りたくない値がある場合は、tie の中で ignore を指定してください。たとえば、最大値だけ受け取るときは次のように指定します。

リスト : ignore の使用例

  int x;
  tie(x, ignore) = max_min(a);

このほかにも tuple には便利な関数が用意されています。詳細はC++のリファレンスマニュアルをお読みください。


初版 2015 年 11 月 28 日
改訂 2023 年 4 月 15 日