std::tupleの値を型指定で取得する
C++ にはstd::pair
とstd::tuple
という複数の型の値を保持できる便利なクラスがあります。std::pair
は値を2個しか保持できないのですが、std::tuple
は2個以上保持できます。コンパイラによって保持できる最大数が変わりますが、10個までは保証されています。
std::pair
とstd::tuple
には色々なユースケースがあると思いますが、以下の2つが一番便利だと思います。
- 以下のような複数の出力引数を持つ関数を、
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) { ... }
- 以下のように複数の変数をセットとして比較したい時にも使えます。
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
に同じ型の値が複数入っている場合はビルドエラーになります。
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
を付けると予想外な挙動になる場合があります。
以下の場合は、s1
とs2
とn
はちゃんと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::tuple
がconst
になっているだけで、参照型のnum1
とnum2
はconst
になっていないです。なので、参照型で作成されるstd::tuple
にはconst
を付けてもあまり意味がないです。
Discussion