std::iter_swapをフック可能にしてほしい。配列が二つあったとして、その片方をキーとして使って、両方の配列を同時にsortしたいと言う気持ちがある。int>のようなものを作ってstd::sortと適当なコンパレータを組み合わせて行う。});だがpairでなくて配列を二つ持っていることもあろう。5};例えばtransformする手はある。x.second;});長い。それにコピーが入ってしまう。自分でsortを実装すれば回避できるのだが、あまりやりたいことではない。質のいい実装があるならそれを使えばいいのだ。言うわけで、複数のイテレータを一つにまとめるzip_iteratorを作ることにした。片方を並べ替えるときにもう片方がついてくるようにすればいい。これをする上で必要なのは、zip_iteratorから取り出した参照を何らかの形で書き込み可能なようにすることである。これに関しては、std::tieが返すオブジェクトと同じものを用意すればいいだろう。zip_iteratorの実装zip_iteratorは複数のイテレータをまとめたものなので、std::tuple<Iterator...>をラップしてイテレータのように振る舞わせれば良い。イテレータとして振る舞わせるためには、基本的にはインクリメント・デクリメントとoperator*・operator->による値の取り出しがあれば良い。インクリメントは順に全部やっていけばいいので、例えばシンプルに再帰で実装するなら以下のようになる。*this;}operator*は少し面倒だ。これはIterator::referenceを返すので、zip_iteratorの気持ち的にはstd::tuple<Iterator::reference...>を作って返したい。困った時は最終形から始めて、必要なものを明らかにしよう。...>になればいいので、最終的には以下のような関数があれば良いことになる。);}当然ながらこれはそのままでは動かない。sizeof...(Iterator)のように展開して渡すところにあるのだが、標準ライブラリにちょうどこのための機能がある。前の記事でも出てきたstd::index_sequenceだ。...);}index_sequenceは、この型の値そのものは特に使い道がないが、型そのものに出てくる値が重要な珍しいタイプのクラスだ。templateならコンパイル時に展開することができるので、必要なものをまとめてindex_sequenceとして渡してやれれば、上記でしているように「コンパイル時にその場で全部展開して渡す」というような操作が可能になる。これを作るためのヘルパークラスがある。C++日本語リファレンスまた、pointerを返す場合でも全く同じ操作が可能だ。ポインタのシンタックスが値のそれと異なるためにreferenceの場合よりも簡単と言っていい。いうわけでこれらは解決した。問題点さて、ちょっとしたテストを書いてzip_iteratorが思った通りに動くこと、関数の戻り値などが思った通りの型になっていること、zip_iteratorを通して書き込みができることなどを確かめたのち、意気揚々とstd::sortにzip_iteratorを渡した時だった。何が問題になっているかと言うと、案の定zip_iterator::operator*()だ。operator*();};そう、これはIterator::referenceが入ったstd::tupleをその場で作って返すのである。関数から返った瞬間はできたてほやほやでまだどんな値にも束縛されていない。これはrvalueだ。*b)のようなことをする。ここでstd::swapを見ていただきたい。rhs);これは左辺値を取るのである。入れ替えるのだから、代入もするだろう。*b)すると、引数は両方右辺値になってしまう!というわけで無理になってしまったわけだ。ご存知の通りswapはフック可能だ。Lookupなる機能があり、std::swapはユーザー定義型に対してはより効率的な実装が与えられる可能性を考慮して実装されているので、標準ライブラリ内部で使われる時は大抵以下のようになっている。b);こうすると、ADLによってaとbが所属する名前空間にあるswap関数がまず検索され、なかった場合にusingされているstd::swapが適用される。aとbが所属する名前空間により効率的なswap実装が提供されていれば、そちらが適用されるわけだ。今回の問題は、返すものがstd::tupleであるということだ。ユーザー定義名前空間にあるものではない。このような解決策を取ることはできない。ユーザー定義名前空間にstd::tupleの薄いラッパーを用意すれば解決可能ではあるのだが、zip_iteratorが返すものが全て独自tuple型になってしまうことを考えるとあまり気持ちのいいものではない。細かいことをいうと、実際に<algorithm>で使われているものの多くはstd::iter_swapだ。これはそれぞれのイテレータが指す先にあるものをswapするというものなので、これをフックできれば一番丸く収まったのだが(zip_iteratorはユーザー定義クラスだ)、何故か全ての関数がexplicitにstd::iter_swapを呼んでいた。これではADLの力は発揮できない。非常に薄いstd::tupleのラッパーを用意してADLがswapを解決したのち最適化パスのどこかでその薄いラッパーが消え失せることを期待する(これはまともなコンパイラを使っているなら十分抱いていい期待だと思う)しかない。一応std名前空間の禁じられた扉を開くことでも実行可能だが、そういうのは実行可能とは言わない。おとなしくペアの配列に変換するか、thrustやBoost.Computeにあるsort_by_keyを呼ぶのがよいだろう。17:20zip_iteratorとstd::iter_swapTweet広告を非表示にする関連記事2017-08-25FizzBuzz会話をしていると、FizzBuzzの話になった。MetaProgramming入門C++ではtemplateを使ってコンパイル時に計算を行うことができる...2017-01-26関数の時間計測C++のthreadライブラリを使っている。2016の5日目の記事です。(7)はてなブログをはじめよう!tniinaさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。