⛓️

[C++] <ranges>のviewを見る16 - reverse_view

3 min read

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
  }
}

逆順にするという操作の都合上、入力となるrangebidirectional_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_viewbegin()の処理結果はキャッシュされることが規定されています。これによってreverse_viewbegin()の計算量は償却定数となります。

views::reverse

reverse_viewに対応するrange adaptor objectstd::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_viewreverse_iteratorsubrange)に対してはその元になっている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
  }
}