🍪

std::tupleの値を型指定で取得する

2024/09/29に公開

C++ にはstd::pairstd::tupleという複数の型の値を保持できる便利なクラスがあります。std::pairは値を2個しか保持できないのですが、std::tupleは2個以上保持できます。コンパイラによって保持できる最大数が変わりますが、10個までは保証されています。

std::pairstd::tupleには色々なユースケースがあると思いますが、以下の2つが一番便利だと思います。

  1. 以下のような複数の出力引数を持つ関数を、std::tupleを使えば、全部纏めて戻り値として返すように変えられます。
void my_func(const int input, int& output1, std::string& output2, double& output3) { ... }

#include <tuple>
std::tuple<int, std::string, double> my_func(const int input) { ... }
  1. 以下のように複数の変数をセットとして比較したい時にも使えます。
bool MyClass::operator<(MyClass const &other) const
{
    return std::tie(m_value1, m_value1, m_value1) < std::tie(other.value1, other.value1, other.value1);
}

std::tieは渡された引数からstd::tupleを作成してくれるユーティリティ関数です。

ただ、std::tupleの値は以下のようにインデックスで取得しないといけないため、正直少し使いづらいです。

std::tuple<std::string, std::string, int> t("foo", "bar", 7);
std::string s1 = std::get<0>(t);  // s1 == "foo"
std::string s2 = std::get<1>(t);  // s2 == "bar"
int i = std::get<2>(t);           //  i == 7

std::tupleで纏められた値を1つずつアンパッキングしないといけないです。

C++14 から以下のように型指定でも取得出来るようになりました。が、std::tupleに同じ型の値が複数入っている場合はビルドエラーになります。

awesome!
int i = std::get<int>(t);                  // i == 7
std::string s = std::get<std::string>(t);  // ビルドエラー

型が全部違う場合は便利ですが、std::tupleの使いづらさが解消されないです。

でも C++17 で導入された構造化束縛で以下のように一行でアンパッキングできるようになりました。

auto [s1, s2, n] = t;        // s1 == "foo", s2 == "bar", n == 7

これで一気に使いやすくなりましたね。

ただし、std::tupleを構造化束縛でアンパッキングする際に注意しないといけないことがあります。constを付けると予想外な挙動になる場合があります。

以下の場合は、s1s2nはちゃんとconstになっていて、値を変えることはできないです。

const auto [s1, s2, n] = t;
n = 2; // ビルドエラーになります。

でも以下の場合は、ビルドエラーにならないです。

int x = 5;
int y = 4;
const auto [num1, num2] = std::tie(x, y);
num1 = 5; // ビルドエラーにならない

なぜかというと、std::tieが返すstd::tupleには渡された変数が参照型で入っているため、内部では以下のような挙動になります。

const std::tuple<int&, int&> t = {x, y};
int& num1 = std::get<0>(t);
int& num2 = std::get<1>(t);

内部で作成されるstd::tupleconstになっているだけで、参照型のnum1num2constになっていないです。なので、参照型で作成されるstd::tupleにはconstを付けてもあまり意味がないです。


|cpp記事一覧へのリンク|

Discussion