[C++] <ranges>のviewを見る10 - drop_view
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_view
のrangeは元のシーケンスのrangeカテゴリと同じになります。
また、このdrop_view
のbegin()
の呼び出しは元のシーケンスがforward_range
(以上の強さ)であるとき、begin()
の処理結果はキャッシュされることが規定されています。これによってdrop_view
のbegin()
の計算量は償却定数となります。
ただし、drop_view<R>
の元のシーケンスR
に対してconst R
がrandom_access_range
かつsized_range
である時、キャッシュは使用されずbegin()
メンバ関数はconst
修飾されます。その場合、drop_view
はconst
状態でもイテレート可能となります。
views::drop
drop_view
に対応するrange adaptor objectがstd::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