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
Tetsuro Matsumura
December 21, 2024
300
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
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
310
The Illustrated Children's Guide to Kubernetes
chrisshort
51
53k
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.2k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.2k
KATA
mclloyd
PRO
35
15k
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
How GitHub (no longer) Works
holman
316
150k
How to Ace a Technical Interview
jacobian
281
24k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2k
From π to Pie charts
rasagy
0
230
It's Worth the Effort
3n
188
29k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
220
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 となる。