[C++] <ranges>のviewを見る6 - filter_view

1 min read読了の目安(約1700字

filter_view

filter_viewは受け取った述語に従って元となるrangeの要素を選別したシーケンスを生成するViewです。

#include <ranges>

int main() {
  // 奇数をフィルターする(偶数のみ取り出す)
  std::ranges::filter_view fv{std::views::iota(1, 10), [](int n) { return n % 2 == 0; }};
  
  for (int n : fv) {
    std::cout << n; // 2468
  }
}

名前の通りに、元となるシーケンスの要素をフィルターしたシーケンスを得るものです。

filter_viewrangeカテゴリは、元となっているrangeのカテゴリによってbidirectional rangeからinput rangeまで変化します。ただ、一番強くてもbidirectional rangeになります。

入力となるシーケンスもフィルタ条件も任意であり結果もまたシーケンスとなるので、filter_viewは色々なところで活躍できそうです。

遅延評価

range adopter全てがそうなのですが、filter_viewによるシーケンス生成は遅延評価されます。構築時に最初の要素が計算され、残りの要素はイテレータのインクリメントのタイミングで計算されます。

// 構築時に条件を満たす最初の要素が探索され、`filter_view`の1番目の要素が計算される
std::ranges::filter_view fv{std::views::iota(1, 10), [](int n) { return n % 2 == 0; }};

auto it = std::ranges::begin(fv);

// 次に条件を満たす要素が探索され、`filter_view`の2番目の要素が計算される
++it;

int n = *it; // 2番目の要素(4)が得られる

この例ではiota_viewもまた遅延評価されているので、一連のシーケンス全体が遅延評価によって生成されています。

この様に、filter_viewに限らずrange adaptorによる処理チェーンは多くの場合可能な限り遅延評価されます。

views::filter

filter_viewに対応するrange adaptor objectstd::views::filterです。

#include <ranges>

int main() {
  for (int n : std::views::filter(std::views::iota(1, 10), [](int n) { return n % 2 == 0; })) {
    std::cout << n;
  }

  std::cout << '\n';

  // パイプラインスタイル
  for (int n : std::views::iota(1, 10) | std::views::filter([](int n) { return n % 2 == 0; })) {
    std::cout << n;
  }
}

views::filterもカスタマイゼーションポインオブジェクトであり、rangeと述語オブジェクトを受け取りそれをそのまま転送してfilter_viewを構築して返します。関数呼び出しによって使う場合あまり恩恵はありませんが、パイプラインスタイルで使用するといい感じに書くことができます。