[C++] <ranges>のviewを見る15 - common_view
common_view
common_viewは元となるシーケンスを、begin()/end()によって取得できるイテレータと終端イテレータの型が同じとなるシーケンスに変換するViewです。
int main() {
auto even_seq = std::views::iota(1)
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::take(10);
// イテレータ型と終端イテレータ型が合わないためエラー
std::vector<int> vec(std::ranges::begin(even_seq), std::ranges::end(even_seq)); // ng
std::ranges::common_view common{even_seq};
std::vector<int> vec(std::ranges::begin(common), std::ranges::end(common)); //ok
for (int n : vec) {
std::cout << n; // 2468101214161820
}
}
これはC++20以降のイテレータ(rangeライブラリのViewなどのイテレータ)をC++17以前のイテレータを受け取るものに渡す際に使用します。
標準コンテナのイテレータペアを受け取るコンストラクタや<algorithm>の各種アルゴリズム群など、C++17以前のイテレータはbegin()/end()の型が同一である事を前提としています。しかし、C++20以降のイテレータおよびrangeは異なっていることが前提です。特に、各種Viewクラスの場合は元となるrangeの種別や構築のされ方によってbegin()/end()の型は細かく変化するので、古いライブラリと組み合わせる際にcommon_viewは必須でしょう。
<algorithm>のイテレータアルゴリズム関数群はstd::ranges名前空間の下にある同名の関数を利用すればC++20以降のイテレータに対してもそのまま使用できるようになっていますが、標準コンテナのイテレータペアを取るコンストラクタや<numeric>にあるアルゴリズム関数などではcommon_viewを使用する必要があります。
int main() {
auto even_seq = std::views::iota(1)
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::take(10);
auto common = std::views::common(even_seq);
// 古いやつ
auto it1 = std::find_if(common.begin(), common.end(), [](int n) { return 10 < n;});
std::cout << *it1 << '\n'; // 12
// 新しいやつ
auto it2 = std::ranges::find_if(even_seq.begin(), even_seq.end(), [](int n) { return 10 < n;});
std::cout << *it2 << '\n'; // 12
// むしろrange直接
auto it3 = std::ranges::find_if(even_seq, [](int n) { return 10 < n;});
std::cout << *it3 << '\n'; // 12
// 古いやつ
auto sum = std::accumulate(common.begin(), common.end(), 0u);
std::cout << sum << '\n'; // 110
// まだない・・・
//auto sum2 = std::ranges::accumulate(even_seq.begin(), even_seq.end(), 0u);
//auto sum3 = std::ranges::accumulate(even_seq, 0u);
}
common_viewは元となるrangeがforward_range以上であればforward rangeとなり、それ以外の場合はinput rangeとなります。
common_range
begin()/end()の型が同一であるrangeはstd::ranges::common_rangeコンセプトによって表現され、そのようなrangeをcommon rangeと呼びます。
template<class T>
concept common_range =
range<T> && same_as<iterator_t<T>, sentinel_t<T>>;
C++20以降のrange/イテレータライブラリにおいては、begin()から得られるものをイテレータ、end()から得られる終端イテレータの事を番兵(Sentinel)と呼び分けます。Sentinelという言葉は既にrangeライブラリ周りでは当たり前のように使われています、慣れましょう。
views::common
common_viewに対応するrange adaptor objectがstd::views::commonです。
#include <ranges>
int main() {
auto even_seq = std::views::iota(1)
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::take(10);
auto common1 = std::views::common(even_seq);
std::vector<int> vec(std::ranges::begin(common1), std::ranges::end(common1)); //ok
for (int n : vec) {
std::cout << n; // 2468101214161820
}
}
#include <ranges>
int main() {
// パイプラインスタイル
auto even_seq = std::views::iota(1)
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::take(10)
| std::views::common;
std::vector<int> vec(std::ranges::begin(even_seq), std::ranges::end(even_seq)); //ok
for (int n : vec) {
std::cout << n; // 2468101214161820
}
}
views::commonはカスタマイゼーションポイントオブジェクトであり、rangeオブジェクト(r)を1つ受け取り、それがすでにcommon_rangeならばstd::views::all(r)の結果を、common_rangeでないならばrを転送してcommon_viewを構築して返します。
結果の型を区別しなければ、あらゆるrangeに対してcommon_view相当のものを得ることができます。特にcommon_viewはcommon_rangeから構築することができないので、common_viewが欲しい際は常にviews::commonを利用するとよりジェネリックです。
Discussion