⛓️

[C++] <ranges>のviewを見る10 - drop_view

2020/10/22に公開

drop_view

drop_viewは元となるシーケンスの先頭から指定された数の要素を取り除いたシーケンスを生成するViewです。

#include <ranges>

int main() {
  // 先頭から5つの要素を取り除く
  std::ranges::drop_view dv{std::views::iota(1, 10), 5};
  
  for (int n : dv) {
    std::cout << n; // 6789
  }
}

ちょうどtake_viewと逆の働きをするもので、ループ的に考えるなら先頭から指定個数の要素をスキップしたところから開始するシーケンスを生成します。

オーバーラン防止と遅延評価

take_viewがそうであるようにdrop_viewもまた元となるシーケンスの長さを超えることの無いようになっています。

drop_viewによるシーケンスは遅延評価によって生成され、そのイテレータの取得時(begin()の呼び出し時)に元となるシーケンスの先頭イテレータを指定した分進めて返します。その際、元のシーケンス上で終端チェックを行いながら進めることでオーバーランしないようになっています。これはC++20から追加されたstd::ranges::next(it, end, n)を使用して行われます。

// drop_view構築時にはまだ何もしない
std::ranges::drop_view dv{std::views::iota(1, 10), 5};

// イテレータ取得時にスキップ処理が行われる
// 元のシーケンスの先頭イテレータを指定した長さ進めるだけ
// その際終端チェックを同時に行う
auto it = std::ranges::begin(tv);

// その他の操作は元のシーケンスのイテレータそのまま
++it;
*it;

イテレータは元のシーケンスのものを完全に流用するので、drop_viewrangeは元のシーケンスのrangeカテゴリと同じになります。

また、このdrop_viewbegin()の呼び出しは元のシーケンスがforward_range(以上の強さ)であるとき、begin()の処理結果はキャッシュされることが規定されています。これによってdrop_viewbegin()の計算量は償却定数となります。

ただし、drop_view<R>の元のシーケンスRに対してconst Rrandom_access_rangeかつsized_rangeである時、キャッシュは使用されずbegin()メンバ関数はconst修飾されます。その場合、drop_viewconst状態でもイテレート可能となります。

views::drop

drop_viewに対応するrange adaptor objectstd::views::dropです。

#include <ranges>

int main() {
  for (int n : std::views::drop(std::views::iota(1, 10), 5)) {
    std::cout << n;
  }
  
  std::cout << '\n';

  // パイプラインスタイル
  for (int n : std::views::iota(1, 10) | std::views::drop(5)) {
    std::cout << n;
  }
}

views::dropはカスタマイゼーションポイントオブジェクトであり、2つの引数を受け取りそれらに応じたViewを返します。その条件は複雑なので割愛しますが、例えばrandom_access_rangeかつsized_rangeである標準ライブラリのもの(std::span, std::string_viewなど)に対しては、与えられた長さと元の長さのより短い方の位置から開始するように構築し直したその型のオブジェクトを返します。

厳密にはdrop_viewだけを返すわけではありませんが、結果の型を区別しなければ実質的にdrop_viewと同等のViewが得られます。

Discussion