C++03 で使える reference_wrapper
導入
C++0x の std::reference_wrapper は operator() を参照先に forward するので、関数オブジェクトを要求する関数 (STL アルゴリズムなど) にそのまま渡すことができます。
しかし、boost でこれに相当すると思われる boost::reference_wrapper にはこの機能がありません。そのため、ユーザー側で参照を保持する関数オブジェクトを作ろうとすると boost::bind(boost::ref(f), _1) などと書かなければならなくなり、面倒です。Boost ではライブラリ側で boost::reference_wrapper を識別する戦略をとっていますが (Boost.Thread など)、関数オブジェクトを扱う全てのライブラリでそれを行うのも、これまた面倒です。
そこで、自分で reference_wrapper を書こうとなるわけです。そのために、まず次の2つの問題を解決します。
- 戻り値の型: C++03 には decltype がないため、戻り値の型を計算するのが困難です。これは boost::result_of を使って対応します。
- 引数の forwarding: C++03 には rvalue reference がないため、perfect forwarding を行うことができません。The Forwarding Problem: Arguments にはいくつかの解決策が挙げられていますが、今回は #3 のアプローチを取ります。すなわち、non-const reference を取るオーバーロードと const reference を取るオーバーロードを用意します。上のペーパーでは線形時間で実装できない (2^N 個のオーバーロードが必要になる) ことが欠点としてあげられていますが、それは Boost.PP が華麗に解決してくれるでしょう。
それでは、C++0x の std::reference_wrapper の仕様 (20.8.3) に従って実装します。
https://gist.github.com/923083
使ってみる
関数オブジェクトへの参照を保持するのが基本的な使い方です。
template <class T> struct summation { T value; summation() : value(0) {} typedef void result_type; typedef T argument_type; void operator()(T x) { value += x; } private: summation(summation const &); // noncopyable }; int main() { int array[] = {0, 1, 2, 3, 4}; summation<int> sum; std::for_each(array, array + 5, vitro::ref(sum)); std::cout << sum.value << '\n'; // 10 }
関数オブジェクト以外にも、関数、関数ポインタ、メンバ関数ポインタ、メンバオブジェクトポインタへの参照を保持できます。
void say_hello() { std::cout << "Hello, world!\n"; } struct base { void print(int value) const { std::cout << value << '\n'; } }; struct derived : base {}; struct integer { integer(int value) : value(value) {} int value; }; int main() { // 関数への参照 vitro::ref(say_hello)(); // Hello, world! // メンバ関数ポインタへの参照 vitro::cref(&base::print)(base(), 23); // 23 vitro::cref(&base::print)(boost::make_shared<derived>(), 42); // 42 // メンバオブジェクトポインタへの参照 std::cout << vitro::cref(&integer::value)(integer(63)) << '\n'; // 63 }
C++0x の INVOKE のセマンティクス (20.8.2) に従うので、this として shared_ptr を渡したり、メンバオブジェクトポインタを扱うことができます。自由関数 invoke も提供しています。
namespace vitro { template <class F, class A0, class A1, ...> result_of_invoke<F(A0 &, A1 &, ...)> invoke(F &f, A0 &a0, A1 &a1, ...); }
GCC 4.6.0 の std::reference_wrapper は INVOKE をサポートしていません。