前置き
標準の ranges 及び range-v3 はコンセプトが複雑に組み合わさってできているライブラリのため、ところどころの説明を省いています。
起こっていること
根本的な問題になっているのは、std 側の view が range-v3 側の view としては認識されていないことです。
具体的には、std 側のコンセプト std::ranges::view
は
cpp
1namespace std::ranges {
2
3template<class T>
4inline constexpr bool enable_view = derived_from<T, view_base>;
5
6template<class T>
7concept view = range<T> && movable<T> && default_initializable<T> && enable_view<T>;
8
9} // namespace std::ranges
のようになっています。
std::ranges::view
であるためには std::ranges::enable_view
が true
である必要があり、明示的な特殊化が無ければ std::ranges::view_base
を継承しているかによって決まります。
対して range-v3 側のコンセプト ranges::view_
は
cpp
1namespace ranges {
2
3namespace ext {
4
5template<class T>
6struct enable_view : std::is_base_of<view_base, T> {};
7
8} // namespace ext
9
10template<class T>
11inline constexpr bool enable_view = ext::enable_view<T>::value;
12
13template<class T>
14concept view_ = range<T> && semiregular<T> && enable_view<T>;
15
16} // namespace ranges
のようになっています。
同じ名前で同じ仕組みになっていますが、両者は名前空間が別れており、完全に別々のものです。
そのため std_rng
は std 側から見れば view でも range-v3 側から見ればただの range でしかありません。
ここで、 view とはそもそも要素の列を(基本的には)参照するだけの軽量なラッパーとして設計されているために、 view ではない range の右辺値を扱うことができません。
※ C++20 には新しく std::ranges::owning_view
という、 range の右辺値を扱えるようにする view が入ったため、 std 側では事情がやや異なります。
cpp
1// NG
2auto rng = std::vector{3, 1, 4} | ranges::views::transform(std::identity{});
3
4// OK
5auto vec = std::vector{3, 1, 4};
6auto rng = vec | ranges::views::transform(std::identity{});
質問のコードで std_rng
をそのまま ranges::views::join
に渡そうとするとエラーになりますが、これは ranges::views::join
が対象の range に要求する制約 ranges::views::joinable_range
を満たせないことが原因になります。
cpp
1namespace ranges::views {
2
3template<class Rng>
4concept joinable_range__concept_ =
5 input_range<range_reference_t<Rng>> &&
6 (std::is_reference<range_reference_t<Rng>>::value ||
7 view_<range_reference_t<Rng>>);
8
9template<class Rng>
10concept joinable_range =
11 viewable_range<Rng> && input_range<Rng> && joinable_range__concept_<Rng>;
12
13} // namespace ranges::views
ranges::views::joinable_range
の定義を上に示しましたが重要なのは std::is_reference<range_reference_t<Rng>>::value || view_<range_reference_t<Rng>>
の部分で、 range_reference_t<Rng>
を RR とすれば、RR が input_range
を満たすことの他に **「RR が参照型である」または「RR が ranges::view_
を満たす」**必要があります。
そのため std::views::transform
に渡しているラムダ式が返すものが std の view ではなく range-v3 の view であれば、後者の制約が満たされコンパイルが通ります。
本命の ranges::views::cache1
ですが、これは view や参照可能な range を受け取り、走査した結果を自身に保存しつつそれに対する参照をイテレータによって提供します。
これにより std::ranges::iota_view<int, int>
がキャッシュされ、 std_rng | ranges::views::cache1
の RR が std::ranges::iota_view<int, int>&
という参照となるため先程の制約のうち前者を満たします。
cpp
1#include <range/v3/all.hpp>
2#include <ranges>
3
4int main() {
5 // RR = std::ranges::iota_view<int, int>
6 // 参照型?→No
7 // view_ ?→No
8 // => ERROR
9 auto std_rng = std::views::iota(0, 5) | std::views::transform([](int a) { return std::ranges::views::iota(0, a); });
10 // auto x = std_rng | ranges::views::join;
11
12 // RR = ranges::iota_view<int, int>
13 // 参照型?→No
14 // view_ ?→Yes
15 // => OK
16 auto ok = std::views::iota(0, 5) | std::views::transform([](int a) { return ranges::views::iota(0, a); });
17 auto y = ok | ranges::views::join;
18
19 // RR = std::ranges::iota_view<int, int>&
20 // 参照型?→Yes
21 // view_ ?→No
22 // => OK
23 auto z = std_rng | ranges::views::cache1 | ranges::views::join;
24}
対処
最初に言及をした通り、根本的な問題は std の view と range-v3 の view に関係性が何もないことです。なので、 std の view が range-v3 の view として見做されるよう、 enable_view
を特殊化することで、質問のコードは問題なく動作します。
cpp
1#include <iostream>
2#include <ranges>
3#include <range/v3/view/concat.hpp>
4#include <range/v3/view/join.hpp>
5#include <range/v3/view/cache1.hpp>
6
7template <class V>
8 requires std::ranges::enable_view<V>
9inline constexpr bool ranges::enable_view<V> = true;
10
11int main() {
12 auto std_rng = std::ranges::views::iota(0,5) | std::ranges::views::transform([](const auto a){return std::ranges::views::iota(0,a);});
13 auto v3_rng = std_rng | ranges::views::join;
14 std::cout << ranges::views::concat(v3_rng, v3_rng, v3_rng) << std::endl;
15}
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/12/13 12:44