[C++] テンプレートクラスの内部クラスの継承における基底クラスのメンバ変数検索

 タイトルがごちゃごちゃしてますが、C++の仕様の中でもそこそこカオスな分類に入る(と個人的に思っている)名前検索でハマったので、そのへん調べたメモとしてまとめておきます。テンプレートを使った際の名前探索に関するお話です。

内部クラスの継承

 普通に書くとこんな感じ。

class outer
{
    class base
    {
    public:
        int x;
    };

    class derived : public base
    {
        void hoge()
        {
            x = 10;
        }
    };
};

 特におかしなところはありません。コンパイルも通ります。しかし、outerクラスをテンプレートクラスにするとコンパイルが通らなくなります。

template<typename T>
class outer
{
    class base
    {
    public:
        int x;
    };

    class derived : public base
    {
        void hoge()
        {
            x = 10; // xなんて見つからないよ!
        }
    };
};

 上記の例ではxに10を代入している部分でコンパイルエラーになります。なぜでしょうか。

名前の検索

 C++03言語標準(n1905。ドラフトです)には以下のような記述があります。

Section 14.6/8
When looking for the declaration of a name used in a template definition, the usual lookup rules (3.4.1, 3.4.2) are used for non-dependent names. The lookup of names dependent on the template parameters is postponed until the actual template argument is known (14.6.2)

 直訳するとこんな感じ

テンプレート定義中に利用されている名前の宣言の探索時には、通常の名前探索規則(3.4.1, 3.4.2)は非依存名に適用される。 テンプレートパラメータに依存する名前の探索は、実際のテンプレート引数が知られるまで遅延される(14.6.2)。

 なんのこっちゃ、だと思いますが、簡単に言うと

  1. テンプレートパラメータに依存する名前は、テンプレート実体化時に検索される(検索が遅延される)
  2. テンプレートパラメータに依存しない名前は、定義時に検索される(一般的な挙動)

 ということです(この名前探索の仕組みを「two phase name lookup」と呼びます)。詳しく説明するには「名前とはなんぞや?」みたいな話になるので割愛します。要するに、テンプレートパラメータに関連するかどうかで検索のタイミングが異なる、ということです。

 ここまで来ると大方わかってくるかと思いますが、内部クラスはテンプレートパラメータに依存しますから、テンプレートクラスの実体化時まで内部クラスは名前探索の対象となりません。したがって、クラス宣言時に参照できるのは非依存名だけであり、基底クラスのメンバは検索されないのです。outerがテンプレートクラスでない場合は、全ての名前が非依存名ですから、基底クラスのメンバも検索されます。

 さて困りました、継承元のクラスのメンバにアクセス出来ないのであれば継承を使う意味がありません。解決方法はいくつかあります。一つ目は、outerの外側でbaseやderivedを定義する方法です。テンプレートクラスの中で定義されるために名前探索ができなくなっているので、外に出してやれば解決します。

 もう一つは、以下の定義に関連します。

Section 14.6.2/3
In the definition of a class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.
クラステンプレートまたはクラステンプレートのメンバ定義中において、テンプレートクラスの基底クラスがテンプレート引数に依存する場合、基底クラスのスコープはテンプレートクラスまたはメンバの定義時点、または、テンプレートクラスまたはメンバの実体化時点、のいずれかの非修飾名探索の間探索対象とならない。

 訳間違ってるかも。ざっくり言えば非修飾名がアレなのでちゃんと修飾してやれば検索されますよ、ってことです。

template<typename T>
class outer
{
    class base
    {
    public:
        int x;
    };

    class derived : public base
    {
        void hoge()
        {
            base::x = 10; // 基底クラス名を指定する
            this->x = 10; // あるいは自身のクラスに所属していることを明記
        }
    };
}

 これでコンパイルが通ります。このtwo phase name lookupしかり、ADLしかり、名前探索には罠(っぽい仕様)がたくさんありますねぇ…

 ちなみに今回は内部クラスの継承という形で紹介しましたが、例えばテンプレートクラスを継承したテンプレートクラスを作った場合にも同様の問題が起こります。たぶんそっちのほうが起こりやすいのかなぁ…

0 件のコメント :

コメントを投稿