2014年5月18日日曜日

ダブルディスパッチの対象をテンプレートクラスにしたら動かなくなった

以下はダブルディスパッチの例
#include <iostream>

struct Sub;

struct Super
{
    virtual void Func(Super&) = 0;
    virtual void Func(Sub&) = 0;
};

struct Sub : public Super
{
    void Func(Super& a)
    {
        std::cout << "Sub::Func(Super&)" << std::endl;
        a.Func(*this);
    }
    void Func(Sub& a)
    {
        std::cout << "Sub::Func(Sub&)" << std::endl;
    }
};

int main()
{
    Super* a = new Sub();
    Super* b = new Sub();
    a->Func(*b);
    return 0;
}
出力結果は以下のとおり

Sub::Func(Super&)
Sub::Func(Sub&)

Sub は自分自身の型を知っているので、オーバーロードされた適切な関数を呼ぶことが出来る。 勿論、オーバーロードされた関数でなくとも良い。

ところが、以下のコードはエラーとなる。
#include <iostream>

template<typename T>
struct Sub;

struct Super
{
    virtual void Func(Super&) = 0;
    template<typename T>
    virtual void Func(Sub<T>&) = 0;
};

template<typename T>
struct Sub : public Super
{
    void Func(Super& a)
    {
        std::cout << "Sub::Func(Super&)" << std::endl;
        a.Func(*this);
    }
    void Func(Sub& a)
    {
        std::cout << "Sub::Func(Sub&)" << std::endl;
    }
};

int main()
{
    Super* a = new Sub<int>();
    Super* b = new Sub<int>();
    a->Func(*b);
    return 0;
}
テンプレート関数は仮想化することが出来ない。 何故かというと、テンプレート関数は呼び出しを見つけた際に、型に応じた関数を生成するので、 仮想関数テーブルがクラスの定義から一意に決定出来なくなってしまうからだ。

スーパークラス側でテンプレートを決定してしまえば問題は解決するが、そうすると様々な型同士で処理可能にするという目的を達成できない。 さて、どうしたものか……