[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