[C++] <ranges>のviewを見る12 - join_view
join_view
join_view
はViewのrangeとなっているシーケンスを平坦化したシーケンスを生成するViewです。
#include <ranges>
int main() {
std::vector<std::vector<int>> vecvec = { {1, 2, 3}, {}, {}, {4}, {5, 6, 7, 8, 9}, {10, 11}, {} };
std::ranges::join_view jv{vecvec};
for (int n : jv) {
std::cout << n; // 1234567891011
}
}
すなわち、配列の配列を1つの配列に直列化するような事を行うものです。
join_view
のrangeは通常、元となるシーケンスの外側と内側のrangeが両方ともbidirectional_range
以上であればbidirectional rangeとなり、forward_range
以上であればforward rangeとなります。
それ以外の場合、及び外側のrangeのイテレータの*
がprvalueを返すような場合には常にinput rangeになります。
この例ではstd::vector
のstd::vector
を利用していますが、別に外側と内側のrangeが同じものである必要はありません。std::list
のstd::vector
とか、std::deque
の生配列など、rangeのrangeになっていればjoin_view
は平坦化してくれます。。
#include <ranges>
int main() {
std::vector<std::list<int>> veclist = { {1, 2, 3}, {}, {}, {4}, {5, 6, 7, 8, 9}, {10, 11}, {} };
std::ranges::join_view jv1{veclist};
for (int n : jv1) {
std::cout << n; // 1234567891011
}
std::cout << '\n';
std::deque<int> arrdeq[] = { {1, 2, 3}, {}, {}, {4}, {5, 6, 7, 8, 9}, {10, 11}, {} };
std::ranges::join_view jv2{arrdeq};
for (int n : jv2) {
std::cout << n; // 1234567891011
}
}
遅延評価
join_view
によるシーケンスもまた遅延評価によって生成されます。join_view
の仕事の殆どは元となるシーケンスの内側のrangeを接続することにあり、イテレータのインクリメントのタイミングでそれを行います。
join_view
は元となるシーケンスの内側のシーケンスのイテレータを利用する事で1つの内側rangeのイテレートを行います。そのイテレータが終端に達した時(1つの内側rangeの終端に達した時)、外側rangeのイテレータを一つ進めてそこから次の内側rangeのイテレータを取得します。
そのままだと内側rangeが空の場合に死ぬので、すぐに内側rangeの終端チェックを行い空でない内側rangeが見つかるまで外側rangeをイテレートします。
std::vector<std::vector<int>> vecvec = { {1, 2, 3}, {}, {}, {4}, {5, 6, 7, 8, 9}, {10, 11}, {} };
// 構築とイテレータ取得時には何もしない
std::ranges::join_view jv{vecvec};
auto it = std::ranges::begin(jv);
// インクリメント時に内側イテレータの接続を行う
// 内側イテレータが終端に到達していれば、外側イテレータを進めてそこから内側イテレータを再取得する
// 同時に内側イテレータの終端チェックを行い、空の内側*range*をスキップする
++it;
// デクリメント時はその逆を行う
--it;
// 間接参照は元のシーケンスのイテレータそのまま
int n = *it;
1つの内側rangeをイテレートしている間は内側イテレータの終端チェックのみが行われますが、終端に到達した時(2つの内側rangeを接続する時)は少し処理が重くなります。
次の図は、配列の配列になっているシーケンスとjoin_view
の様子をそれっぽく書いたものです。
views::join
join_view
に対応するrange adaptor objectがstd::views::join
です。
int main() {
std::vector<std::vector<int>> vecvec = { {1, 2, 3}, {}, {}, {4}, {5, 6, 7, 8, 9}, {10, 11}, {} };
for (int n : std::views::join(vecvec)) {
std::cout << n;
}
std::cout << '\n';
// パイプラインスタイル
for (int n : vecvec | std::views::join) {
std::cout << n;
}
}
views::join
はカスタマイゼーションポイントオブジェクトであり、rangeのrangeとなっているrangeオブジェクト1つを受け取りそれを転送してjoin_view
を構築して返します。
Discussion