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_viewranges::iota_viewranges::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 ✅