[C++] <ranges>のviewを見る3 - iota_view
iota_view
iota_view
は渡された2つの値をそれぞれ始点と終点として、単調増加するシーケンスを作成するViewです。
整数型に限定するならば、init, bound
の2つの値を渡すと[init, bound)の範囲で1づつ増加していく数列を生成します。
#include <ranges>
int main() {
std::ranges::iota_view iv{1, 10};
for (int n : iv) {
std::cout << n; // 123456789
}
}
また、引数1つだけで構築した場合は終端のない無限列を生成します。
#include <ranges>
int main() {
std::ranges::iota_view iv{1};
for (int n : iv) {
std::cout << n; // 1234567891011121314151617181920
if (n == 20) break; // 何かしら終わらせる条件がないと無限ループ
}
}
このような無限列は、range adaptorと呼ばれるViewと組み合わせる事で有効活用することができます(後の方で紹介予定)。
基本的には整数列を生成するために使用すれば良いのですが、この実体はインクリメント可能であり距離を定義できさえすればどんな型の単調増加シーケンスでも作成可能です。これはstd::weakly_incrementable
コンセプトによって制約されます。
例えば、ポインタ型やイテレータ型のシーケンスを作成可能です。
int main() {
int array[] = {2, 4, 6, 8, 10};
// ポインタのインクリメントシーケンス
std::ranges::iota_view iva{std::ranges::begin(array), std::ranges::end(array)};
for (int* p : iva) {
std::cout << *p; // 246810
}
std::cout << '\n';
std::list list = {1, 3, 5, 7, 9};
// listイテレータのインクリメントシーケンス
std::ranges::iota_view ivl{std::ranges::begin(list), std::ranges::end(list)};
for (auto it : ivl) {
std::cout << *it; // 13579
}
}
正直このジェネリックな振る舞いは使いどころがあるのか分かりません。頭の片隅に置いておくと役立つこともあるでしょうか・・・?
なお、<ranges>
のViewはおおよそ全てがこの様になるべく無駄にジェネリックに動作可能なように設計されています。
なお、浮動小数点数型はstd::weakly_incrementable
を満たさないのでiota_view
では使用できません。距離を整数値で定義できないのが原因のようです。
遅延評価
iota_view
によって生成されるシーケンスはiota_view
オブジェクトを構築した時点では生成されておらず、その生成は遅延評価されます。
具体的には、iota_view
オブジェクトから得られるイテレータのインクリメント(++i/i++
)のタイミングで1つづつシーケンスの要素が計算されます。
int main() {
std::ranges::iota_view iv{1, 10}; // この段階ではまだ何もしてない
auto it = std::ranges::begin(iv); // この段階ではまだ何もしてない
int n1 = *it; // 初項(1)が得られる
++it; // ここで次の項(2)が計算される
it++; // ここで次の項(3)が計算される
int n2 = *it; // 3番目の項(3)が得られる
}
rangeカテゴリ
iota_view
の表すrangeはその要素型によってrangeカテゴリが変化します。整数型で使用する分には常にrandom access rangeですが、そのほかの型の場合は可能な操作によって弱くなる可能性があります。例えば、std::list
のイテレータによるiota_view
オブジェクトはbidirectional rangeになります。
random access rangeとなるiota_view
のシーケンスは、そのイテレータに対して--, +=, -=
等でシーケンス上をほぼ自由に移動でき、bidirectional rangeとなる場合は++, --
によって前後方向への移動が可能になります。
range factories
iota_view
にもrange factoryとなる関数オブジェクトが用意されています。
#include <ranges>
int main() {
for (int n : std::views::iota(1, 10)) {
std::cout << n; // 123456789
}
std::cout << '\n';
for (int n : std::views::iota(1)) {
std::cout << n; // 1234567891011121314151617181920
if (n == 20) break;
}
}
このstd::views::iota
はカスタマイゼーションポイントオブジェクトで、1つか2つの引数を受け取りその引数をそのまま転送してiota_view
オブジェクトを生成し返します。
Discussion