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,
}
views::repeat
range factoriesとしての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