C++23 <ranges>のviewを見る1 - Repeat view
views::repeat
views::repeatは与えられた1つの値の繰り返しからなるシーケンスを生成するviewです。これはシーケンスを生成するタイプのviewであるRangeファクトリの1つなので、Rangeアダプタに対する入力として使用するものです。
import std;
int main() {
for (int n : std::views::repeat(23) | std::views::take(4)) {
std::ptrint("{}, ", n);
}
// 23, 23, 23, 23,
}
- [Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ(実行例は動作させるために一部変更しています)
デフォルトでは、与えられた値を永遠に繰り返す無限列が生成されます。
views::repeatには、生成するシーケンスの要素となる値に加えて、2つ目の引数でシーケンスの要素数(繰り返し回数)を指定することもできます。
import std;
int main() {
for (int n : std::views::repeat(23, 4)) {
std::ptrint("{}, ", n);
}
// 23, 23, 23, 23,
}
range factoriesとしてのviews::repeat
views::repeatはカスタマイゼーションポイントオブジェクトであり、1つか2つの入力を受けて、それをそのまま転送してrepaet_viewオブジェクトを構築して返します。
repeat_view
repeat_viewはviews::repeatの実装詳細であり、views::repeatの実装の全てです。
namespace std::ranges {
// repeat_viewの宣言例
template<move_constructible T, semiregular Bound = unreachable_sentinel_t>
requires (is_object_v<T> && same_as<T, remove_cv_t<T>> &&
(integer-like-with-usable-difference-type<Bound> ||
same_as<Bound, unreachable_sentinel_t>))
class repeat_view : public view_interface<repeat_view<T, Bound>> {
...
};
}
要素型であるTはオブジェクト型でありCV修飾がないことの他にはmove_constructibleであることだけが求められています。
Boundは要素数を抑える型で、デフォルト時はunreachable_sentinelが使用され、要素数を指定する場合はほぼ整数型を求めています。
要素数を指定しない場合(Bound = unreachable_sentinel_tの場合)には.end()から得られる番兵としてunreachable_sentinelが使用され、unreachable_sentinelは任意のイテレータとの比較に常にtrueを返す番兵であるため、イテレータの終端判定を最適化によって除去しやすくなっています。ただし、これによって.begin()と.end()の型が異なってしまうため、common_rangeではなくなります(要素数を指定する場合は同じイテレータ型が使用されます)。
要素数が指定されている(Boundがunreachable_sentinel_tではない)場合にのみ、repeat_viewはsized_rangeかつcommon_rangeになります。
動作詳細
repeat_viewの生成するシーケンスは、当然のことながら、その構築時にシーケンス中の要素の全てがどこかの領域に敷き詰められて保持されているものではなく、実際には同じ1つの値を異なるイテレータが参照することによって構成されています。
そのため、そのイテレータの間接参照で得られるものはrepeat_viewに渡されて内部で保持されている値のconst左辺値参照となります。
このことにより、repeat_viewはその要素としてムーブオンリーな型の値からなるシーケンスを生成することができます。
import std;
int main() {
std::ranges::repeat_view rv{std::make_unique<int>(23), 4}; // ok
for (const auto& up : rv) {
std::print("{}, ", &up);
}
// 全て同じアドレスが出力される
}
(GCCの実装バグにより動作例なし・・・)
また、この性質によってrepeat_viewは要素型などに関係なく常にrandom_access_rangeとなります。contiguous_rangeではないのは、イテレータの進行と要素のメモリ配置が同期しない(動かない)ためです。
要素数を指定しない場合でもrepeat_viewはrandom_access_rangeであるため、その場合のrepeat_viewは進行/後退の両方向について無限列となります。
views::takeとviews::dropの特殊対応
repeat_viewは内部に格納している値をそのシーケンスの全ての要素としていることとrepeat_viewそのものに要素数を指定可能であることから、指定された個数の要素を切り出す/スキップするような処理はかなり単純化できる可能性があり、実際にviews::takeとviews::dropではそれが行われています。
views::takeの場合は、views::take(r, n)のように呼ばれた時に、r(の素の型R)がrepeat_viewであれば、次のようにrepeat_viewを調整して返します
-
Rがsized_rangeである(要素数が指定されている)場合-
rの内包する値をvalue、ranges::distance(r)とnの短い方をlとして -
views::repeat(value, l)を返す
-
- それ以外の(
repeat_viewの要素数が指定されていない)場合-
rの内包する値をvalueとして -
views::repeat(value, n)を返す
-
views::dropの場合は、views::drop(r, n)のように呼ばれた時に、r(の素の型R)がrepeat_viewであれば、次のようにrepeat_viewを調整して返します
-
Rがsized_rangeである(要素数が指定されている)場合-
rの内包する値をvalue、ranges::distance(r)をm、mとnの短い方をlとして -
views::repeat(value, m - l)を返す
-
- それ以外の(
repeat_viewの要素数が指定されていない)場合-
auto(r)を(rをdecay-copyして)返す
-
どの場合でも、元のrepeat_viewオブジェクトrから取り出された値(value)はrの値カテゴリによって適切にムーブorコピーされます。
int main() {
auto inf_rep = std::views::repeat(23);
// 無限`repeat_view`に対するtake/drop
auto t1 = inf_rep | std::views::take(10);
// t1 = std::views::repeat(23, 10) と等価
auto d1 = inf_rep | std::views::drop(10);
// d1 = auto(inf_rep) と等価
auto fin_rep = std::views::repeat(23, 10);
// 有限`repeat_view`に対するtake/drop、元の長さに収まる場合
auto t2 = fin_rep | std::views::take(5);
// t2 = std::views::repeat(23, 5) と等価
auto d2 = fin_rep | std::views::drop(5);
// d2 = std::views::repeat(23, 10 - 5) と等価
// 有限`repeat_view`に対するtake/drop、元の長さを超える場合
auto t3 = fin_rep | std::views::take(15);
// t3 = std::views::repeat(23, 10) と等価
auto d2 = fin_rep | std::views::drop(15);
// d3 = std::views::repeat(23, 10 - 10) と等価
}
views::repeatと各種特性
最後に、views::repeat(repeat_view<T, B>)のrangeとしての各種特性をまとめておきます
-
reference:const T& -
rangeカテゴリ :random_access_range -
common_range:Bがunreachable_sentinel_tではない時- すなわち、要素数を指定した時
-
sized_range:common_rangeと同じ -
const-iterable: 〇 -
borrowed_range: ×
これらはそれぞれ次のような意味です
-
reference: イテレータの関節参照の結果型 -
rangeカテゴリ : モデルとなる最も強いrangeコンセプト -
common_range:begin()の戻り値型とend()の戻り値型が同じであるか-
common_rangeコンセプトのモデルとなるか
-
-
sized_range:ranges::size()によって定数時間で要素数を得られる-
sized_rangeコンセプトのモデルとなるか
-
-
const-iterable:const修飾されているときでもrangeとなるか -
borrowed_range: 範囲の寿命とそこから取得したイテレータの寿命が切り離されている-
borrowed_rangeコンセプトのモデルとなるか
-
Discussion