最終更新日時(UTC):
が更新

履歴 編集

class template
<ranges>

std::ranges::take_view(C++20)

namespace std::ranges {
  template<view V>
  class take_view : public view_interface<take_view<V>> { …… }; // (1)

  namespace views {
    inline constexpr /*unspecified*/ take = /*unspecified*/;     // (2)
  }
}

概要

  • (1): 元のRangeの先頭から指定した個数の値を取り出すview
  • (2): take_view、または(1)の動作を実現するviewを生成するRangeアダプタオブジェクト

takeで得られるviewの大きさは、指定した個数に関わらず、元のRangeの大きさを超えることは無い。

Rangeコンセプト

borrowed sized output input forward bidirectional random_access contiguous common viewable view
(1) (1) (1) (1) (1) (1) (1) (1) (1)
  • (1): Vに従う

テンプレートパラメータ制約

効果

take_viewでラップする必要が無い型ではtake_viewを使わないようになっている。

メンバ関数

名前 説明 対応バージョン
(constructor) コンストラクタ C++20
base Vの参照を取得する C++20
begin 先頭を指すイテレータを取得する C++20
end 番兵を取得する C++20
size 要素数を取得する C++20

rを元のRangeとする。sizeranges::size(r)が有効な式であるときに定義される。

継承しているメンバ関数

名前 説明 対応バージョン
empty Rangeが空かどうかを判定する C++20
operator bool Rangeが空でないかどうかを判定する C++20
data Rangeの先頭へのポインタを取得する C++20
front 先頭要素への参照を取得する C++20
back 末尾要素への参照を取得する C++20
operator[] 要素へアクセスする C++20
cbegin 定数イテレータを取得する C++23
cend 定数イテレータ(番兵)を取得する C++23

推論補助

名前 説明 対応バージョン
(deduction_guide) クラステンプレートの推論補助 C++20

使用上の注意

このview(及びRangeアダプタ)は、入力がrandom_access_rangeではない場合に意図しない無限ループに陥ることがある

int main() {
  for (auto i  : std::views::iota(0)
               | std::views::filter([](auto i) { return i < 10; })
               | std::views::take(10))
  {
    std::cout << i << '\n';
  }
}

このコードは0から9までの数字を出力するだけのループに見えるが、実行すると無限ループに陥るか完了までに多大な時間を要する。

範囲for文は内部でイテレータを用いた通常for文に展開されており、1つのループの終わりでは次の様な順番でループのための処理が行われている

  1. イテレータのインクリメント
  2. イテレータの終端チェック
  3. ループ本体実行

この例における1では、次の様なことが起きている

  1. take_viewイテレータのインクリメント
    1. take_view内部カウンタの減算
    2. filter_viewのイテレータのインクリメント
      1. 条件を満たす次の要素の探索
        • 条件を満たす要素が見つかるまで、iota_viewイテレータをインクリメントする
          1. iota_viewイテレータのインクリメント
            • 増分1で整数値を生成、この場合は終端がない
          2. iota_view生成値の読み取りと条件チェック

views::iota(0)によるシーケンスは0始まりの整数の無限列であり、filter_viewのフィルタ条件(return i < 10)は10未満の整数値のみを取り出すものである。したがってこの場合、iota_viewが10を生成して以降はfilter_viewで条件を満たす要素は存在しなくなる。

ループの最終端、本体処理が9を出力した後のループ終了直前には、take_viewの内部カウンタが0になりtake_viewの終了条件が満たされるものの、その直後に内部イテレータ(filter_viewのイテレータ)をインクリメントしてしまう。filter_viewのイテレータはインクリメントによって10未満の次の要素を探索するためにiota_viewのイテレータをインクリメントし、iota_viewは10以降の整数値をひたすら生成し続ける。

この例のiota_viewの要素型は符号付き整数型であるため、そのオーバーフローは未定義動作となり、このループが終了するかどうかは保証されない。

入力に対してfilter_viewの条件の与え方が悪いという見方もできるが、これはtake_viewイテレータのインクリメント時に起こることに問題があり、take_viewのイテレータは入力がrandom_access_rangeではない場合はcounted_iteratorを使用するため、本質的にはcounted_iteratorの問題である。

counted_iteratorはインクリメント時にカウンタ値を減算してからラップしているイテレータをインクリメントするが、カウンタ値を考慮せず常にインクリメントを行うためcounted_iteratorの内部カウンタが0になりその終了条件が満たされた時でもラップするイテレータをインクリメントしてしまう。そのため、filter_viewイテレータのようにそのイテレータ取得時点で終端が確定していないイテレータを入力として使用すると、その終端でこのような問題が起こりうる。

特に、input_iteratorでは、イテレータがその範囲の終端に達した後でインクリメントしてしまうと何が起こるかわからないため、この問題はより複雑な形で顕在化する可能性がある。

int main() {
  // 入力ストリームには整数値1つしかない
  auto iss = std::istringstream("0");

  // 0を読んだ後、takeイテレータが進行するが、istream_viewが次のストリーム入力を待機するために終了しない
  for (auto i : std::ranges::istream_view<int>(iss)
              | std::views::take(1))
  {
    std::cout << i << '\n';
  }
}

この例では、istream_viewの基底のストリームに追加の要素が入力されるか、ストリームが閉じられるまでistream_viewのイテレータはインクリメント時に次の要素の入力を待機し続けてしまう。より一般的なinput_iteratorではどうなるかわからない。

ただし、take_viewは入力範囲がrandom_access_rangeの場合はcounted_iteratorを使用しないため、random_access_rangeに対して使用する場合はこの問題は起こらない。

#include <ranges>
#include <iostream>

int main() {
  using namespace std;

  for (int i : views::iota(1) | views::take(5)) {
    cout << i;
  }
}

出力

12345

バージョン

言語

  • C++20

処理系

参照