std::forward は Perfect Forward にのみ使われるとは限らない

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 の operator() に対して、 n という int 型の lvalue を渡しているからです。
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(x) は std::move(x) と同じ効果を持ちます。
そして、 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(x) は std::move(x) と同じ効果を持ちます。*4
一方で T が参照*5の場合には、 std::move(x) と std::forward(x) では異なる動作になり、
std::move(x) を呼び出した場合には、 x の参照先の変数が move される一方、
std::forward(x) を呼び出した場合には、参照先は move されず、 x がそのまま返されます。
そして通常、 lvalue reference の参照先において参照元が move されるような動作は、
再び値を代入する std::swap のようなケースを除き、参照元では想定されていない場合が殆どです。
つまり、 T 型の変数 x を move したい場合で、 T が参照型になり得るような状況では、
一般に std::move(x) ではなく std::forward(x) を使ったほうが安全なのです。


このように、 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