質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

1回答

953閲覧

Range-v3ライブラリとRangeライブラリを組み合わせたコードの挙動がわかりません

UMA821

総合スコア27

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

1クリップ

投稿2021/12/12 05:28

前提・実現したいこと

C++20にてRangeライブラリが導入されたので,Range-v3ライブラリを組み合わせて使用したのですが,良くわからない挙動をしたのでそこについて解説してほしいです.

該当のソースコード

C++

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 7int main() { 8 auto std_rng = std::ranges::views::iota(0,5) | std::ranges::views::transform([](const auto a){return std::ranges::views::iota(0,a);}); 9 auto v3_rng = std_rng | ranges::views::cache1 | ranges::views::join; 10 std::cout << ranges::views::concat(v3_rng, v3_rng, v3_rng) << std::endl; 11}
[0,0,1,0,1,2,0,1,2,3,0,0,1,0,1,2,0,1,2,3,0,0,1,0,1,2,0,1,2,3]

Wandbox

発生している問題

思い通りにプログラムは出力しているのですが,「ranges::views::cache1」の部分が気になっています.
実験を始めた当初はこれを除いた状態でコンパイルしていたのですが,それだと失敗してしまいました.
適当に試してこれを書き加えたところ,たまたま動いたのです.

試したこと

Range-v3 User Manualを確認すると,「ranges::views::cache1」は最新の要素をキャッシュする役割があるようなのですが,これを書き加えたところで何の意味があるのでしょうか.
誰か詳しい方,教えてくださると嬉しいです.

補足情報(FW/ツールのバージョンなど)

Wandbox gcc HEAD 12.0.0

yohhoy👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

前置き

標準の 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_viewtrue である必要があり、明示的な特殊化が無ければ 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 11:32

yaito3014

総合スコア209

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

UMA821

2021/12/13 12:44

回答ありがとうございます! とても分かりやすい説明で疑問点が色々と解消されました. 参考になります.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問