namespace std::ranges {
template<view V>
class take_view : public view_interface<take_view<V>> { …… }; // (1)
namespace views {
inline constexpr /*unspecified*/ take = /*unspecified*/; // (2)
}
}
概要
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
に従う
テンプレートパラメータ制約
view<V>
効果
- (2):
E
およびF
を式、型T
をremove_cvref_t<decltype((E))>
、型D
をrange_difference_t<decltype((E))>
とする。式views::take(E, F)
の効果は以下の通りdecltype((F))
がconvertible_to<D>
のモデルでなければ、呼び出しは不適格T
がranges::empty_view
の特殊化であれば、((void) F,decay-copy(E))
と等しい。ただし、E
とF
の評価順序は不定順で序列化(indeterminately sequenced)されるT
がrandom_access_range
およびsized_range
のモデルであり、かつ次のいずれかの特殊化であるとき、T(ranges::begin(E),ranges::begin(E) +min<D>(ranges::size(E), F))
と等しい。ただし、E
は1度だけ評価されるspan
(ただし、T::extent ==dynamic_extent
であること)basic_string_view
ranges::iota_view
ranges::subrange
- それ以外のとき、
ranges::take_view(E, F)
take_view
でラップする必要が無い型ではtake_view
を使わないようになっている。
メンバ関数
名前 | 説明 | 対応バージョン |
---|---|---|
(constructor) |
コンストラクタ | C++20 |
base |
V の参照を取得する |
C++20 |
begin |
先頭を指すイテレータを取得する | C++20 |
end |
番兵を取得する | C++20 |
size |
要素数を取得する | C++20 |
r
を元のRangeとする。size
はranges::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では、次の様なことが起きている
take_view
イテレータのインクリメントtake_view
内部カウンタの減算filter_view
のイテレータのインクリメント- 条件を満たす次の要素の探索
- 条件を満たす要素が見つかるまで、
iota_view
イテレータをインクリメントするiota_view
イテレータのインクリメント- 増分1で整数値を生成、この場合は終端がない
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
処理系
- Clang: 13.0.0 ✅
- GCC: 10.1.0 ✅
- ICC: ?
- Visual C++: 2019 Update 10 ✅