Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Rangeアダプタを作る
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Tetsuro Matsumura
December 21, 2024
290
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Rangeアダプタを作る
C++MIX #12
https://cppmix.connpass.com/event/337028/
Tetsuro Matsumura
December 21, 2024
Featured
See All Featured
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.5k
RailsConf 2023
tenderlove
30
1.5k
エンジニアに許された特別な時間の終わり
watany
107
250k
My Coaching Mixtape
mlcsv
0
150
Fireside Chat
paigeccino
42
4k
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
540
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
320
Measuring & Analyzing Core Web Vitals
bluesmoon
9
870
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.2k
Product Roadmaps are Hard
iamctodd
PRO
55
12k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
201
75k
The Cult of Friendly URLs
andyhume
79
6.9k
Transcript
Rangeアダプタを作る 2024-12-20 C++ MIX #12 Tetsuro Matsumura
目次 1. Rangeアダプタ 2. Rangeアダプタの性質 3. Rangeアダプタオブジェクト 4. Rangeアダプタクロージャオブジェクトとなる要件 5.
Rangeアダプタクロージャオブジェクトを作る 6. Rangeアダプタオブジェクトを作る 7. ジェネレーターでviewを作る 1
Rangeアダプタ 2 C++20でRangeアダプタが導入され、様々なRangeを簡単に加工できるようになった。 using namespace std; vector v {1, 2,
3, 4, 5}; // reverse: 逆順にする println("{}", v | views::reverse) // [5, 4, 3, 2, 1] // take(n): 先頭からn個とる println("{}", v | views::reverse | views::take(3)) // [5, 4, 3] これを作りたい C++20ではRangeアダプタの性質をすべて満たす汎用的な実装は不可能 ⚫ 何がRangeアダプタとなるのかが規定されていない ⚫ 特定の処理系における実装は、標準ライブラリを真似すればできる C++23では可能
Rangeアダプタの性質 3 Rangeアダプタクロージャオブジェクト(RACO): ⚫ rangeを受け取る単項関数オブジェクト (C++23) ⚫ パイプライン記法で、 | 演算子の右側に現れるオブジェクト
⚫ r をrange, raco をRangeアダプタクロージャオブジェクトとするとき、 r | raco と raco® は等しい Rangeアダプタオブジェクト(RAO): ⚫ 第1引数に viewable_range をとり view を返すカスタマイゼーションポイントオブ ジェクト ⚫ range 以外の引数をすべて渡すと、Rangeアダプタクロージャオブジェクトを返す ⚫ r をrange, rao をRangeアダプタオブジェクトとするとき、 r | rao(…) と rao(r, …) と rao(…)(r) は等しい
Rangeアダプタオブジェクト 4 using namespace std; vector v {1, 2, 3,
4, 5}; // 追加の引数を受け取る前のtakeはRAO、take(3)の値はRACO v | views::take(3); // RACO take(3) に対して関数記法でvを渡す views::take(3)(v); // RAO take に対してすべての引数を渡す views::take(v, 3); // reverse は追加の引数がなく、初めからRACO v | views::reverse; views::reverse(v);
Rangeアダプタクロージャオブジェクトとなる要件 5 C++23では、型 T のオブジェクト t がRACOとなる要件が定められた。 ⚫ t はRangeを受け取る単項関数オブジェクトである
⚫ T は std::ranges::range_adaptor_closure<T> の派生クラスである ⚫ T 以外の型 U に対して、 T は std::ranges::range_adaptor_closure<U> の 派生クラスではない ⚫ T は range のモデルではない 以上の要件を満たせば、自作したクラスのオブジェクトでもRACOになる。 range_adaptor_closure 自体は空のクラス: namespace std::ranges { template<class D> requires is_class_v<D> && same_as<D, remove_cv_t<D>> class range_adaptor_closure { }; }
Rangeアダプタクロージャオブジェクトを作る 6 RACOの実装例: class my_adaptor_closure : public std::ranges::range_adaptor_closure<my_adaptor_closure> { public:
template <std::ranges::viewable_range R> constexpr auto operator()(R&& r) const { return my_adaptor_view(r); // rをラップしたviewを作って返す処理 } }; constexpr my_adaptor_closure my_adaptor; // 使用例 // v | my_adaptor my_adptor_viewクラスはあらかじめ定義しておく
Rangeアダプタクロージャオブジェクトを作る 7 Rangeを受け取る単項関数オブジェクトをRACOに変換する例(P2387R3): template <typename F> class closure_t : public
std::ranges::range_adaptor_closure<closure_t<F>> { F f; public: constexpr closure_t(F f) : f(f) { } template <std::ranges::viewable_range R> constexpr auto operator()(R&& r) const { return f(std::forward<R>(r)); } }; このようなものを用意すれば、ラムダ式で簡単にRACOを定義できる。
Rangeアダプタクロージャオブジェクトを作る 8 例: 標準の join_view を作る別のRACOを定義する (viewまで作るのは大変なので) inline constexpr closure_t
user_defined_join = []<std::ranges::viewable_range R> (R&& r) { return std::ranges::join_view(std::forward<R>(r)); }; // 使用例 // v | user_defined_join;
Rangeアダプタオブジェクトを作る 9 追加の引数があるビューの場合、それらを受け取ってRACOを構築するためには Rangeアダプタオブジェクト(RAO) を定義する必要がある。 関数オブジェクトをラップしてRAOを定義する例 (P2387R3, closure_t は前述の定義): template
<typename F> class adaptor_t { F f; public: constexpr adaptor_t(F f) : f(f) { } template <typename... Args> constexpr auto operator()(Args&&... args) const { if constexpr (std::invocable<F const&, Args...>) { // すべての引数が渡されたら、持っている関数を呼ぶ return f(std::forward<Args>(args)...); } else { // range 以外の引数が渡されたら、後ろから部分適用してRACOを作る return closure_t(std::bind_back(f, std::forward<Args>(args)...)); } } };
Rangeアダプタオブジェクトを作る 10 例: 標準の take_view を作る別のRAOを定義する inline constexpr adaptor_t user_defined_take
= []<std::ranges::viewable_range R> (R&& r, std::range_difference_t<R> d) { return std::ranges::take_view(std::forward<R>(r), d); }; // 使用例 // v | user_defined_take(3);
ジェネレーターでviewを作る 11 std::generator は view であるため、ジェネレーターを使えば簡単に実装できる。 inline constexpr closure_t my_all
= []<std::ranges::viewable_range R> (R&& r) -> std::generator<std::ranges::range_value_t<R>> { // 受け取ったRangeの要素をそのまま出力する for(auto&& i : r1) co_yield i; }; // 使用例 // v | my_all; ここでは、ラムダ式でジェネレーターを定義し、 前述のclosure_tでラップしてRACOにしている。 ジェネレーターはinput_rangeにしかならないので、Rangeの性質としてはそれほど良く ない。元のRangeの性質を温存するにはviewクラスの実装を頑張るしかない。
ジェネレーターでviewを作る 12 concat の実装例 inline constexpr adaptor_t concat = []<std::ranges::viewable_range
R1, std::ranges::viewable_range R2> requires std::same_as< std::ranges::range_value_t<R1>, std::ranges::range_value_t<R2>> (R1&& r1, R2&& r2) -> std::generator<std::ranges::range_value_t<R1>> { // 受け取った2つのRangeの要素を出力する for(auto&& i : r1) co_yield i; for(auto&& i : r2) co_yield i; }; // 使用例 // v1 | concat(v2);
13 まとめ ⚫ C++23では、オブジェクトがRangeアダプタクロージャオブジェクトと なるための要件が定められた。 ⚫ std::ranges::range_adaptor_closure を継承することで、 Rangeアダプタクロージャオブジェクトを自作できる。 ⚫
ジェネレーターはviewなので、Rangeアダプタの自作において便利である。 ただし、input_range となる。