operator ->* について考えてみた。

ちょぃと operator -> のオーバーロードする機会があったのですが、ふと「operator ->* ってのもオーバーロードできたんだよなぁ…」ということを思い出してみたわけです。
今まで ->* はオーバーロードした事無い為、良い機会だからオーバーロードしてみようぜ!

ということで弄ってみたわけですが…

俺的結論。



 要らない子

データメンバ(メンバ変数)を参照する為に ->* をオーバーロードしてみる

これは何も問題なくオーバーロードできちゃいます。


#include <stdio.h>

class C
{
public:
    int     m_nValue;

    int&    operator ->* (int C::* mptr)
    {
        return this->*mptr;
    }

};


int main()
{
    C   c;
    int C::*mp = &C::m_nValue;

    c->*mp = 10;

    printf("%d\n", c.m_nValue);

    return 0;
}

クラス固定でもいいですが、テンプレートとして汎用性を高める事の方が良いような気がするので、実際はこんなコードがでてきますかねぇ。


#include <stdio.h>

template<typename T>
class my_auto_ptr
{
public:

    my_auto_ptr(T* p)
        : m_pObject(p){}

    ~my_auto_ptr()
    {
        delete m_pObject;
    }

    template<typename U>
    U& operator ->* (U T::* mptr)
    {
        return m_pObject->*mptr;
    }

    T* get()
    {
        return m_pObject;
    }

private:

    T*  m_pObject;

};

struct Kanaria
{
    int     m_nValue;
};

int main()
{
    my_auto_ptr<Kanaria> ptrData(new Kanaria);
    ptrData->*(&Kanaria::m_nValue) = 10;

    printf("%d\n", ptrData.get()->m_nValue);

    return 0;
}

とまぁこんな感じで、データメンバについては利用可能であるようです。

メンバ関数呼び出しとして ->* をオーバーロードしてみる

おさらいとして、メンバ関数を関数ポインタ経由で呼び出す場合


#include <stdio.h>

class Manna
{
public:
    int Func(int n)
    {
        return n * 2;
    }
};

int main()
{
    Manna  manna;
    Manna* p_manna = &manna;
    int (Manna::*m_ptr)(int) = &Manna::Func;

    (manna.*m_ptr)(10);
    (p_manna->*m_ptr)(20);

    return 0;
}

なんて書きますが、コレと同じ構文で呼び出したいワケです。 いざ書いてみると…


#include <stdio.h>

class Manna
{
public:

    RET operator ->* (ARG)
    {
        return ;
    }

    int Func()      { return 0; }
    int Func2(int)  { return 1; }
};

int main()
{
    Manna  manna;
    Manna* p_manna = &manna;
    int    (Manna::*m_fun_ptr)(int) = &Manna::Func2;

    (p_manna->*m_fun_ptr)(10);

    return 0;
}

operator ->* の 中身・引数・戻り値 を書くのに困りはじめます。 色々問題点はありますが、とりあえず operatro ->* のボディでは、実際の関数呼び出しの為に呼ぶことが出来ません。

上記の場合では Manna::Func2 に 10 という引数を与えて呼ぼうとしてますが、operatro ->* の中ではこの 10 という値が取得できない為に この中で関数を呼ぶ事は出来ません。

んーじゃぁ、thisを格納しつつ operator() をオーバーロードしてあるプロキシオブジェクトを返してみますか?


#include <stdio.h>

class Manna
{
public:

    class Manna_mem_fun_proxy
    {
    public:

        Manna_mem_fun_proxy(Manna* p, int (Manna::*fnPtr)(int))
            : m_pcParent(p)
            , m_fnPtr(fnPtr){}

        template<typename RET>
        RET operator () (int n)
        {
            return (m_pcParent->*m_fnPtr)(n);
        }

    private:

        Manna*  m_pcParent;
        int     (Manna::*m_fnPtr)(int);
    };

    Manna_mem_fun_proxy operator ->* (int (Manna::*fnPtr)(int))
    {
        return Manna_mem_fun_proxy(this, fnPtr);
    }

    int Func()      { return 0; }
    int Func2(int)  { return 1; }
};

int main()
{
    Manna  manna;
    Manna* p_manna = &manna;
    int    (Manna::*m_fun_ptr)(int) = &Manna::Func2;

    (p_manna->*m_fun_ptr)(10);

    return 0;
}

うむ。これで、呼べる………

って、ある型の関数に特化してるから呼び出せるだけあって…というか、メンバ変数の呼び出し一切考慮してないじゃん!



とまぁ、いろいろ考えてみたのですが、どうしてもネックになるのが 「関す呼び出し時の引数」 なんですよ。これだけは template 使っても対応できませんし…。 エリプシス("..." ね)を使って無限に引数取れるようにして、asm を利用してコールすれば出来るかと思ってみたりしますが、実際に呼び出した時の引数の数なんて分からん気がするので無謀じゃね? と思ってみたり。*1 boost::lambda とかも力技で回避してるぐらいだしなぁ…。




というわけで、俺的結論としては 「operatro ->* のオーバーロード物は組み込みと同じよーに実装するのは無理」 だと思います。多分。

よって、

 要らない子。(ぉ


まぁ、ぶっちゃけ operator ->* のオーバーロードの出番なんてそう無いと思いますケド、実は良い解決方法とかあったりするのかしら〜? 

*1:ちなみに私は未だに asm から call したことが無い(ぉ ^^;