http://d.hatena.ne.jp/gintenlabo/20110108/1294485577#c で、
#include <utility> template<class F, F f> class il_ptr_fun; template<class R, class... Args, R (*pf)(Args...)> struct il_ptr_fun<R (*)(Args...), pf> { typedef R result_type; R operator()( Args... args ) const { return (*pf)( std::forward<Args>(args)... ); } };
この operator() で使われている std::forward の意味が分からない、という指摘が有ったので、
コメントでも書きましたが、改めて解説します。
まず、この operator() で使われてる std::forward は、 Perfect Forward の為のものではありません。
というか、この operator() においては、そもそも Perfect Forward 自体が使われておらず、
仮に operator() を Perfect Forward を使って書き下すと、こうなります:
#include <utility> template<class F, F f> class il_ptr_fun; template<class R, class... Args, R (*pf)(Args...)> struct il_ptr_fun<R (*)(Args...), pf> { typedef R result_type; template<class... As> R operator()( As&&... as ) const { return (*pf)( std::forward<As>(as)... ); } };
このコードは Perfect Forward の典型例なので、何をやってるかは調べれば分かると思います。
さて、では何故この形にしていないか、というと、
引数の型が一致せずコンパイルエラーになった場合に、 Perfect Forward を使っていると
転送関数の内部でコンパイルエラーが発生するため、エラーメッセージが無駄に長くなる、という欠点があるからです。
関数内部でのコンパイルエラーを避けるには、戻り値による SFINAE を使って
#include <utility> template<class F, F f> class il_ptr_fun; template<class R, class... Args, R (*pf)(Args...)> struct il_ptr_fun<R (*)(Args...), pf> { typedef R result_type; template<class... As> auto operator()( As&&... as ) const -> decltype( (*pf)( std::forward<As>(as)... ) ) { return (*pf)( std::forward<As>(as)... ); } };
のように書くことも可能ですが、
その場合、エラーメッセージは「 operator() が見つからなかった」的なメッセージになるので、これも不親切。
また、戻り値の型は R だって最初から分かってるのに、 SFINAE を使うためだけに decltype を使うのは、読む人を無駄に混乱させるだけでしょう。
そもそも今回の場合、関数のシグネチャは分かってるわけですから、
operator() を関数テンプレートにする必要は、実際の所、ありません。
だったら、コンパイル時間が長くなり、エラーメッセージも分かりにくくなる関数テンプレートを使うより、
引数を決め打ちした operator() を使ったほうが良いだろう、ということで、
il_ptr_fun の operator() は Perfect Forward を使ってないのです。
さて、では何故、 operator() が
R operator()( Args... args ) const { return (*pf)( std::forward<Args>(args)... ); }
という形になってるかを説明します。
まず、 Perfect Forward と似た感じの、
R operator()( Args&&... args ) const { return (*pf)( std::forward<Args>(args)... ); }
という形ではダメかというと、これはダメです。
というのも、この関数では、 args... は rvalue reference になるので*1、
operator() に対して lvalue を渡すことが出来なくなるからです。*2
具体的には、 http://ideone.com/VErbJ
#include <utility> template<class F, F f> class il_ptr_fun; template<class R, class... Args, R (*pf)(Args...)> struct il_ptr_fun<R (*)(Args...), pf> { typedef R result_type; R operator()( Args&&... args ) const { return (*pf)( std::forward<Args>(args)... ); } }; inline int square(int n){ return n * n; } #include <iostream> template<class F> inline void print_ptr_fun(F f, int n) { std::cout << f(n) << std::endl; } int main() { print_ptr_fun( il_ptr_fun<int (*)(int), &square>(), 1 ); }
このコードはコンパイルエラーになります。
何故なら、 int&& 型を取る il_ptr_fun
il_ptr_fun の operator() が Args... 型の引数を取っているのは、この問題を避け、 lvalue だろうと rvalue だろうと問題なく受け渡しできるようにするためです。
その場合、コピーコストが気になる人もいるかもしれませんが、実際の所、
コピーコストが気になるような場合には、ラップする関数ポインタ自身のシグネチャが、例えば
void f( large_size_object const& x );
のように参照渡しになる場合が殆どで、その場合は Args... も参照型になるため、特に問題にはなりません。
仮にコピーコストの高いオブジェクトを値渡ししている場合であっても、
il_ptr_fun の内部で使われている std::forward によって move されるので、
move constructor さえきちんと定義してあるならば、オーバーヘッドは殆どありません。
一般に、 T 型のオブジェクト x に対し、 T が参照ではない場合には、 std::forward
そして、 il_ptr_fun 内の std::forward は、実質的に move を行うために使われているのですが、*3
では何故 std::move ではなく std::forward を使っているかというと、
#include <utility> template<class F, F f> class il_ptr_fun; template<class R, class... Args, R (*pf)(Args...)> struct il_ptr_fun<R (*)(Args...), pf> { typedef R result_type; R operator()( Args... args ) const { return (*pf)( std::forward<Args>(args)... ); // std::move(args)... だとダメ } }; #include <string> void twice( std::string& x ) { x += x; } #include <iostream> #include <vector> #include <algorithm> int main() { std::vector<std::string> s = { "hoge", "fuga", "piyo" }; std::for_each( s.begin(), s.end(), il_ptr_fun<void (*)( std::string& ), &twice>() ); std::cout << s[0] << std::endl; }
http://ideone.com/UAbiZ ( move の場合: http://ideone.com/XtnMp )
のように参照を引数に取る場合、 std::move だと問題になるからです。
一般に、 T 型のオブジェクト x に対し、 T が参照ではない場合には、 std::forward
一方で T が参照*5の場合には、 std::move(x) と std::forward
std::move(x) を呼び出した場合には、 x の参照先の変数が move される一方、
std::forward
そして通常、 lvalue reference の参照先において参照元が move されるような動作は、
再び値を代入する std::swap のようなケースを除き、参照元では想定されていない場合が殆どです。
つまり、 T 型の変数 x を move したい場合で、 T が参照型になり得るような状況では、
一般に std::move(x) ではなく std::forward
このように、 std::forward は、別に Perfect Forward のみで使われるものではありません。
むしろ、 std::forward というのは、対象の型を明示して安全にした std::move だ、と考えたほうがよく、
その事を理解しないで std::forward を使うと、例えば Perfect Forward の局面でも、
// やっちゃダメな例 template<class... Args> void f( Args&&... args ) { f1( std::forward<Args>(args)... ); // ここで args... は move されるので f2( std::forward<Args>(args)... ); // この呼び出しは一般に予期せぬ結果になる }
のような間違った使用法をしてしまう恐れがあるので、覚えておいたほうが良いと思います。
// 本当はこれ、 C++0x 標準ライブラリ完全解説 でやるつもりでしたが、折角の機会だったので前倒しで説明しました。
// std::forward に対する詳細な説明は、完全解説の記事の方で、改めて行いたいと思います。
*1: Args... は引数から推論されないので、 Args&&... は、イメージ的には int&& とか std::string&& みたいな、具体的な型への rvalue reference になります
*2: lvalue, rvalue に関しては 本の虫: rvalue reference 完全解説 辺りを参照。
*3:実際には他の理由もあり、 http://ideone.com/XQp9q のような場合にコンパイルエラーを避ける意味もあります
*4:大事なことなので二回言いました
*5:正確に言うなら lvalue reference