2014/03/23 とりあえず今までに思いついた中で、C++ constant inheritanceに一番近い解決法

C++で結構いろんな人が困ってる(らしい)問題のひとつに、const inheritance (勝手に命名。正式にこのように呼ばれてるのかどうかは知らない。)がある。検索した感じだと、read-only inheritanceとか呼んでる人もいるみたいだ。結構多くの人が似た様な問題に直面してるっぽいけど、僕が探した範囲では解決方法は見つからなかった。

どういうことかというと、例えば、

class YsShell
{
public:
    // Something useful
};

class YsShellExt : private YsShell
{
public:
    operator const YsShell &() const;
};

こういうことがしたい。YsShellクラスをprivate継承してYsShellExtを作りたいんだけど、ただ、const YsShell &への変換は自動的にやってほしい。YsShellExtはprivateにYsShellを継承してるけど、const YsShell &への変換はpublicだから、自動的にやってよ、と言いたいわけだが、これはできない。

どうやら、C++の仕様として、privateにクラスを継承してる場合、親クラスへの自動変換は明示的に禁止。仮に明示的に親クラスへの変換オペレータを定義しても、禁止。と、決まっているらしい。

だから、結局、const YsShell &を引き受けてくれる関数に、const YsShellExt &を渡して、自動的に変換してほしいと思ったら、

 YsShellExt shlExt;
SomeFunction((const YsShell &)shlExt);  // Possible, but want to avoid static cast.

と、書かなくてはならない。けど、この方法は、いまいち良くない。なんでかというと、これだと間違ったオブジェクトを与えたときでも、コンパイラの型チェックが機能しない。とにかく、ここで(const YsShellExt &)と書きたくない。C++コンパイラが自動的に変換可能な型を見つけてくれるのが理想的だ。

何かいい方法がないかとずっと思ってたんだけど、つい最近思いついた方法が、コンバーターオブジェクト経由の変換。YsShellExt内に、コンバーターオブジェクトを作ってしまう。具体的には、

class YsShell
{
public:
    // Something useful
};

class YsShellExt : private YsShell
{
public:
    class Converter
    {
    public:
        YsShellExt *shl;
        operator const YsShell &() const
        {
            return *(const YsShell *)shl;
        }
        operator YsShellExt &() const
        {
            return *shl;
        }
    };
    class ConstConverter
    {
    public:
        const YsShellExt *shl;
        operator const YsShell &() const
        {
            return *(const YsShell *)shl;
        }
        operator const YsShellExt &() const
        {
            return *(const YsShellExt *)shl;
        }
    };

    inline Converter Conv(void)
    {
        Converter conv;
        conv.shl=this;
        return conv;
    }
    inline ConstConverter Conv(void) const
    {
        ConstConverter conv;
        conv.shl=this;
        return conv;
    }
};

こう書く。それで、YsShellExtオブジェクトをconst YsShellオブジェクトに変換して渡したいときは、

YsShellExt shlExt;
SomeFunction(shlExt.Conv()); // No static cast.  Compiler will catch if the receiving type is incompatible.

Converterオブジェクトは、const YsShell &、const YsShellExt &、またはYsShellExt &のみに自動変換可能。もしも、shlExtがconstだと、ConstConverterオブジェクトが使われて、この場合は、const YsShell &またはconst YsShellExt &のみに自動変換可能。これによって、static castを避けることができる。変換できないところにオブジェクトを渡したらコンパイラがエラーで知らせてくれる。Converterオブジェクトのサイズは、ポインタひとつ分だから、コピーにかかる時間は無視していい。

あと、徹底的にRIAA路線で行きたい人は、Converterオブジェクトのコンストラクタにポインタを渡すようにしてもいいかもしらんね。 けど、上の書き方だと、単純にConverterオブジェクトはPODのままだから、僕はこの書き方の方が気に入ってる。