[C++] <ranges>のviewを見る16 - reverse_view
reverse_view
reverse_view
は元となるシーケンスを逆順にしたシーケンスを生成するViewです。
#include <ranges>
int main() {
auto seq = std::views::iota(1, 10)
| std::views::filter([](int n) { return n % 2 == 1; })
| std::views::transform([](int n) { return n * 10; });
std::ranges::reverse_view rv{seq};
for (int n : rv) {
std::cout << n; // 9070503010
}
}
逆順にするという操作の都合上、入力となるrangeはbidirectional_range
以上でなければなりません。reverse_view
そのものは入力rangeと同じカテゴリになります。
遅延評価
reverse_view
は遅延評価によって逆順範囲を生成します。とはいえ、実際の逆順範囲に関してはstd::reverse_iterator
に丸投げしているので、reverse_view
の行うことはbegin()
によってイテレータを取得するタイミングでstd::reverse_iterator
を適切に構築することです。
そこではまず、元のシーケンスがcommon_range
であればその終端イテレータ(end()
で取得できるイテレータ)を使ってstd::reverse_iterator
を構築します。そうでない場合はstd::ranges::next()
によって終端イテレータを求めて、それによってstd::reverse_iterator
を構築します。元のシーケンスがcommon_range
ではないbidirectional_range
である場合、終端イテレータの取得はインクリメントによって行われるため、シーケンスの長さをN
としたO(N)
の時間計算量となってしまいます(そのようなイテレータはなかなかない気がしますが)。
auto seq = std::views::iota(1, 10)
| std::views::filter([](int n) { return n % 2 == 1; })
| std::views::transform([](int n) { return n * 10; });
// 構築の時点では何もしない
std::ranges::reverse_view rv{seq};
// イテレータ取得時に元となるシーケンスの終端一つ手前のイテレータからreverse_iteratorを構築して返す
// 元となるシーケンスがcommon_rangeではなく単にbidirectional_rangeである場合、時間計算量はO(N)になる
auto it = std::ranges::begin(rv);
// イテレータの操作はreverse_iteratorと同じ
++it;
--it;
int n = *it;
// 元のシーケンスはcommon_rangeでなくてもok
bool fin = it == std::ranges::end(rv);
また、このreverse_view
のbegin()
の処理結果はキャッシュされることが規定されています。これによってreverse_view
のbegin()
の計算量は償却定数となります。
views::reverse
reverse_view
に対応するrange adaptor objectがstd::views::reverse
です。
#include <ranges>
int main() {
auto seq = std::views::iota(1, 10)
| std::views::filter([](int n) { return n % 2 == 1; })
| std::views::transform([](int n) { return n * 10; });
for (int n : std::views::reverse(seq)) {
std::cout << n; // 9070503010
}
std::cout << '\n';
// パイプラインスタイル
for (int n : seq | std::views::reverse) {
std::cout << n; // 9070503010
}
}
views::reverse
はカスタマイゼーションポイントオブジェクトであり、rangeオブジェクトを1つ受け取りそれを転送してreverse_view
を構築して返します。ただし、すでに逆順になっているシーケンス(reverse_view
やreverse_iterator
のsubrange
)に対してはその元になっているrangeを返す事によって、reverse_view
を何回も適用する事を回避します。
#include <ranges>
int main() {
std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9};
for (int n : vec | std::views::reverse // vector<int> -> reverse_view
| std::views::reverse // reverse_view -> ref_view<vector<int>>
| std::views::reverse // ref_view<vector<int>> -> reverse_view
| std::views::reverse // reverse_view -> ref_view<vector<int>>
)
{
std::cout << n; // 123456789
}
}
Discussion