reference_closureが必要なわけ

http://d.hatena.ne.jp/faith_and_brave/20080326/1206522694#c
http://d.hatena.ne.jp/faith_and_brave/20080328/1206694733
なんで参照渡しの場合だけreference_closureを使えるのかが伝わっていなかったようなので、値渡しの場合の例も書いて説明します。

#include <iostream>

int main()
{
    int count = 3;

    const auto& f = [=](int n){ return n + count; };
    std::cout << f(2) << std::endl; // 5
    ++count;
    std::cout << f(2) << std::endl; // 5
}

g++4.3.0で書くなら次のようなコードになります。

#include <iostream>

int main()
{
    int count = 3;

    class F
    {
    public:
        explicit F(int count) : count(count)
        {
        }

        F(F&& f) : count(f.count)
        {
        }

        int operator()(int x) const
        {
            return x + count;
        }

    private:
        int count;

        F& operator=(const F&); // = delete
    };

    const F& f = F(count);
    std::cout << f(2) << std::endl; // 5
    ++count;
    std::cout << f(2) << std::endl; // 5
}

見ての通り、クロージャオブジェクトの型Fはcountを値で保持しています。
intを引数に取りintを返すクロージャは無数にあり、それらは任意のメンバ変数を持つと考えられます。
なので、もしreference_closureがこれらを保持するとなると、メンバ変数のコピーのために動的メモリが必要になってしまいます。
一方参照だけの場合は、ラムダ式の宣言されたスコープを覗き見ることさえ出来れば、実質メンバ変数は必要ありません。


そこで「スコープを覗き見る」方法ですが、これはC/C++局所変数がどのようにして実現されているかを考えると理解できます。
wikipedia:コールスタック
x86(IA32)の一般的なコンパイラの場合、局所変数はフレームポインタ(EBPレジスタの値)からオフセット指定でアクセスされます。
昨日の例のcountならば、そのアドレスは「EBPの値 - 8」です。
このオフセットはコンパイル時に決定されるので、EBPの値を覚えておけば呼び出し先の任意の関数からこの変数にアクセスすることが可能になります。
昨日のコードでは、reference_closure::frame_がこれに相当します。


これで、キャプチャされる変数が参照だけの場合、関数ポインタとフレームポインタだけを使って実装したreference_closureでクロージャを保存できることは分かったと思います。(スタックが理解できれば!)
当然、これは高々数バイトの軽量オブジェクトです。
動的メモリなしに実現できるわけですから、reference_closureを用意する意義はあります。
その一方で、reference_closureは仮想関数を持たないので、クロージャオブジェクトの型Fをreference_closureから派生させることの必要性は感じません。
唯一の利点は、暗黙的にreference_closureへ変換できることでしょう。
「reference_closureにコピーしない限り、reference_closureから派生しない」となっていればまだマシですが。


なお、autoはreference_closureの完全な代用にはなりません。

void foo()
{
    int a = 0;

    const auto& f1 = [&a](){ return ++a; };
    const auto* pf = &f1;

    const auto& f2 = [&a](){ return ++a; };
    pf = &f2; // エラー: 変換できない
}

同じシグネチャクロージャを統一的に扱う型としてreference_closureが必要になります。