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つの問題を解決します。

  1. 戻り値の型: C++03 には decltype がないため、戻り値の型を計算するのが困難です。これは boost::result_of を使って対応します。
  2. 引数の 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 をサポートしていません。

補足

実装する過程で気づいたことを書いておきます。

  1. boost::decay は cv-qualifier を除去しない。boost::decay::type は int const となる。
  2. VC10 では、boost::remove_pointer が cv-qualified な関数ポインタを除去できない。boost::remove_pointer::type は int () ではなく int (* const)() となる。std::remove_pointer でも同様。

今回はいつも以上に何かを再発明した気がしますが、私自身は勉強になりました。はい。