web-dev-qa-db-ja.com

thisポインターを介してテンプレートの基本クラスメンバーにアクセスする必要があるのはなぜですか?

以下のクラスがテンプレートではない場合、xクラスにderivedを含めることができます。ただし、以下のコードでは、I have to use this->x。どうして?

template <typename T>
class base {

protected:
    int x;
};

template <typename T>
class derived : public base<T> {

public:
    int f() { return this->x; }
};

int main() {
    derived<int> d;
    d.f();
    return 0;
}
175
Ali

簡単な答え:xを従属名にするため、テンプレートパラメータがわかるまでルックアップが延期されます。

長い答え:コンパイラがテンプレートを見ると、テンプレートパラメータを見ることなく、特定のチェックをすぐに実行することになっています。他のパラメータは、パラメータがわかるまで延期されます。これは2フェーズコンパイルと呼ばれ、MSVCはそれを行いませんが、標準で必要であり、他の主要なコンパイラで実装されています。必要に応じて、コンパイラはテンプレートを(ある種の内部解析ツリー表現に合わせて)見た時点でコンパイルし、インスタンス化のコンパイルを後まで延期する必要があります。

テンプレートの特定のインスタンス化ではなく、テンプレート自体で実行されるチェックでは、コンパイラがテンプレート内のコードの文法を解決できる必要があります。

C++(およびC)では、コードの文法を解決するために、何かが型であるかどうかを知る必要がある場合があります。例えば:

#if WANT_POINTER
    typedef int A;
#else
    int A;
#endif
static const int x = 2;
template <typename T> void foo() { A *x = 0; }

aが型の場合、ポインターを宣言します(グローバルxをシャドウする以外の効果はありません)。 Aがオブジェクトの場合、それは乗算です(また、演算子のオーバーロードを禁止することは違法で、右辺値に割り当てます)。それが間違っている場合、このエラーは診断する必要がありますフェーズ1、標準によってエラーであると定義されていますテンプレート内、特定のインスタンス化ではありません。テンプレートがインスタンス化されない場合でも、Aがintの場合、上記のコードは不正な形式であり、fooがテンプレートではなかった場合と同様に診断する必要があります。 、しかし単純な機能。

現在、標準では、テンプレートパラメータに依存するare n'tの名前はフェーズ1で解決可能である必要があるとしています。ここでAは依存名ではなく、 Tと入力します。そのため、テンプレートを定義する前に、フェーズ1で検出およびチェックするために定義する必要があります。

T::AはTに依存する名前になります。フェーズ1では、それが型であるかどうかはわからないでしょう。インスタンス化でTとして最終的に使用されるタイプは、まだ定義されていない可能性が高く、たとえテンプレートパラメーターとして使用されるタイプがわからない場合でも。ただし、不正な形式のテンプレートに対して貴重なフェーズ1チェックを行うには、文法を解決する必要があります。そのため、標準には依存名に関するルールがあります。コンパイラは、typenameで修飾されてareタイプであるか、特定の明確な名前で使用されない限り、コンテキスト。たとえば、template <typename T> struct Foo : T::A {};では、T::Aが基本クラスとして使用されるため、明確に型です。 Fooが、ネストされたタイプAの代わりにデータメンバーAを持つ何らかのタイプでインスタンス化されている場合、それはテンプレートのエラーではなく、インスタンス化を行うコードのエラーです(フェーズ2) (フェーズ1)。

しかし、依存する基本クラスを持つクラステンプレートはどうでしょうか。

template <typename T>
struct Foo : Bar<T> {
    Foo() { A *x = 0; }
};

Aは従属名ですか?基本クラスでは、any nameが基本クラスに表示される場合があります。したがって、Aは従属名であると言うことができ、非タイプとして扱います。これには、Fooのすべての名前が依存するという望ましくない効果があるため、Fooで使用されるすべてのタイプ(組み込みタイプを除く)を修飾する必要があります。 Fooの内部では、次のように書く必要があります。

typename std::string s = "hello, world";

なぜなら、std::stringは従属名であり、したがって、特に指定しない限り非タイプであると想定されるためです。痛い!

優先コード(return x;)を許可する2番目の問題は、BarFooの前に定義されていて、xがその定義のメンバーでない場合でも、誰かが後でBar<Baz>にデータメンバBarが含まれるように、Baz型のxの特殊化を定義してから、Foo<Baz>をインスタンス化します。そのため、そのインスタンス化では、テンプレートはグローバルxを返す代わりにデータメンバーを返します。または、逆にBarの基本テンプレート定義にxが含まれていた場合、それなしで特殊化を定義でき、テンプレートはFoo<Baz>で返すグローバルxを探します。これは、あなたが抱えている問題と同じくらい驚くべきで苦痛を伴うと判断されたと思いますが、驚くべきエラーを投げるのではなく、サイレント驚くべきことです。

これらの問題を回避するために、有効な標準では、クラステンプレートの依存する基本クラスは、名前が他の何らかの理由で既に依存している場合を除き、名前を検索しないだけだとしています。これは、依存ベースで見つかったからといって、すべてが依存状態になるのを防ぎます。また、見ているという望ましくない効果があります-基本クラスからのものを修飾する必要があるか、見つからない場合。 Aを依存させるには、3つの一般的な方法があります。

  • クラス内のusing Bar<T>::A;-Aは現在Bar<T>内の何かを参照しているため、依存しています。
  • 使用時のBar<T>::A *x = 0;-繰り返しますが、Aは間違いなくBar<T>にあります。 typenameが使用されなかったため、これは乗算であるため、悪い例かもしれませんが、operator*(Bar<T>::A, x)が右辺値を返すかどうかを調べるには、インスタンス化まで待つ必要があります。誰が知っている、多分それは...
  • 使用時のthis->A;-Aはメンバーであるため、Fooにない場合は、基本クラスにある必要があります。標準では、これが依存関係になると言われています。

2フェーズコンパイルは手間がかかり、困難であり、コードに余分な冗長性があるという驚くべき要件がいくつかあります。しかし、民主主義のように、それはおそらく他のすべてのことを除いて、おそらく最悪のことをする方法です。

あなたの例では、xが基本クラスのネストされた型である場合、return x;は意味がないと合理的に主張できるので、言語は(a)従属名であると言い、(2)それを扱うべきです非型として、コードはthis->なしで動作します。あなたのケースには当てはまらない問題の解決策からの付随的な被害の程度まであなたはいますが、あなたのベースクラスが潜在的にあなたの下にグローバルを隠す名前を導入するか、あなたが思った名前を持っていないという問題がありますそれらがあり、代わりにグローバルが見つかりました。

また、依存名のデフォルトは反対である(オブジェクトであると何らかの形で指定されていない限りタイプを想定する)か、デフォルトはよりコンテキストに敏感である(std::string s = "";では、std::stringはタイプとして読み取ることができる)と主張することもできますstd::string *s = 0;が曖昧であっても、文法的に意味があります)。繰り返しになりますが、規則がどのように合意されたかはわかりません。私の推測では、必要なテキストのページ数は、コンテキストがタイプを取り、タイプを持たない特定のルールの多くを作成することに対して軽減されます。

248
Steve Jessop

(2011年1月10日からの元の回答)

私は答えを見つけたと思う: GCCの問題:テンプレート引数に依存する基本クラスのメンバーを使用する 。答えはgccに固有のものではありません。


更新:mmichaelのコメント に対応して、C++ 11の draft N3337 から標準:

14.6.2従属名[temp.dep]
[...]
3またはクラステンプレートまたはメンバーのインスタンス化中。

"規格がそう言うから"が答えとしてカウントされるかどうか、私にはわかりません。標準が義務付けている理由を尋ねることができますが、 Steve Jessopの優れた答え および他の人が指摘しているように、この後者の質問に対する答えはかなり長く議論の余地があります。残念ながら、C++標準に関しては、なぜ標準が何かを義務付けているのかについて、簡潔で自己完結的な説明をすることはほとんど不可能です。これは後者の質問にも当てはまります。

12
Ali

xは、継承中は非表示です。次の方法で再表示できます。

template <typename T>
class derived : public base<T> {

public:
    using base<T>::x;             // added "using" statement
    int f() { return x; }
};
11
chrisaycock