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


199

以下のクラスはテンプレートでなかった場合、私は単に持っている可能性がxderivedクラス。ただし、以下のコードでは、を使用する必要があります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;
}

1
ああああ。名前の検索と関係があります。誰かがすぐにこれに答えない場合、私はそれを調べて投稿します(今忙しい)。
templatetypedef

@Ed Swangren:申し訳ありませんが、この質問を投稿したときに、提供された回答の中にそれがありませんでした。その前からずっと答えを探していました。
Ali

6
これは、2フェーズの名前ルックアップ(すべてのコンパイラがデフォルトで使用するわけではない)と依存名が原因で発生します。接頭辞以外のこの問題に対する解決策3、ありxthis->:すなわち、1)接頭辞を使用しbase<T>::x2)の文を追加しusing base<T>::x3)を permissiveモードを有効にグローバルなコンパイラスイッチを使用しますが。これらのソリューションの長所と短所は、stackoverflow.com / questions / 50321788 /…に
ジョージロビンソン

回答:


274

短い答え: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ないため、テンプレートではなく単純な関数と同様に、診断する必要があります。

今、規格はそうではない名前を言うテンプレートパラメータに依存しはフェーズ1 Aで解決可能である必要があるとされています。ここでは依存名ではなく、タイプに関係なく同じことを指しTます。そのため、フェーズ1で検出およびチェックするには、テンプレートを定義する前に定義する必要があります。

T::ATに依存する名前になります。フェーズ1では、それが型かどうかはわかりません。Tインスタンス化のように最終的に使用される型は、まだ定義されていない可能性が高く、たとえそれがテンプレートパラメーターとして使用されるのかわからない場合でも、しかし、不適切なテンプレートの貴重なフェーズ1チェックを行うために、文法を解決する必要があります。したがって、標準には従属名に関するルールがあります。コンパイラーはtypename、それらタイプであることを指定するために修飾されているか、特定の明確なコンテキストで使用されている場合を除き、タイプではないと想定する必要があります。たとえばでは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は依存名ですか?基本クラスを使用すると、任意の名前を基本クラスに含めることができます。したがって、Aは依存名であり、非型として扱うことができます。これは、Fooのすべての名前が依存するという望ましくない影響を与えるため、Fooで使用されるすべての型(組み込み型を除く)を修飾する必要があります。Fooの内部では、次のように記述する必要があります。

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

なぜなら std::string依存名で、従って、特に断りのない限り、非タイプであると仮定なります。痛い!

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

これらの問題を回避するために、実際には、明示的に要求されない限り、クラステンプレートの依存基本クラスは検索の対象と見なされないことが標準で定められています。これは、依存するベースにあるからといって、すべてが依存するのを防ぎます。また、あなたが目にしている望ましくない影響もあります。基本クラスの要素を修飾する必要があるか、または見つかりません。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フェーズコンパイルは手間と困難であり、コードに余分な表現を使用するためのいくつかの驚くべき要件が発生します。しかし、民主主義のように、他のすべてのものとは別に、おそらくそれは物事を行うための可能な最悪の方法です。

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

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


1
ああ、素敵な詳細な答え。私が見上げることを気にしたことがないいくつかのことを明確にしました。:) +1
11

20
@jalf:C ++ QTWBFAETYNSYEWTKTAAHMITTBGOWのようなものはありますか-「答えを知りたい、そしてもっと重要なことをやりたいと思っているかどうかわからない場合を除いて、頻繁に尋ねられる質問」?
Steve Jessop、2011年

4
並外れた答え、質問がFAQに収まるかどうか疑問に思います。
Matthieu M.2011

おっと、百科事典と言えますか?highfive かかわらず、一つの微妙なポイントを:「fooがテンプレートでインスタンス化を行うコードのエラー(フェーズ2)、ないエラーです代わりに、ネストされたタイプAのデータメンバーAを持っているいくつかのタイプ、(相でインスタンス化されている場合1)。」テンプレートは不正な形式ではないと言う方がいいかもしれませんが、これはテンプレート作成者側の誤った仮定または論理バグの場合である可能性があります。フラグが立てられたインスタンス化が実際に意図されたユースケースである場合、テンプレートは間違っています。
Ionoclast Brigham 2013

1
@JohnH。複数のコンパイラーが実装-fpermissiveまたは類似していることを考えると、可能です。実装方法の詳細はわかりませんが、コンパイラーxは実際のテンプレート基本クラスがわかるまで解決を延期する必要がありますT。したがって、原則として非許容モードでは、延期したことを記録し、延期し、一度ルックアップを実行し、ルックTアップが成功したときに提案したテキストを発行します。それが機能する場合にのみ作成されれば、非常に正確な提案になります。ユーザーxが別のスコープから他のスコープを意味する可能性は非常に小さいです!
スティーブジェソップ

13

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

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


更新: C ++ 11標準のドラフトN3337からのmmichaelのコメントへの応答:

14.6.2依存名[temp.dep]
[...]
3クラスまたはクラステンプレートの定義で、基本クラスがtemplate-parameterに依存している場合、非修飾名のルックアップ中に基本クラスのスコープは調べられません。クラステンプレートまたはメンバーの定義のポイント、またはクラステンプレートまたはメンバーのインスタンス化中。

「標準がそう言っているから」が答えとみなされるかどうか、私にはわかりません。標準がそれを義務付けている理由を尋ねることができますが、Steve Jessopの優れた回答や他の人が指摘するように、この後者の質問への回答はかなり長く議論の余地があります。残念ながら、C ++標準に関して言えば、標準が何かを義務付けている理由について、短くて自己完結的な説明をすることはほとんど不可能です。これは後者の質問にも当てはまります。


11

x相続時に隠されています。次の方法で再表示できます。

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

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

25
この答えは、なぜそれが隠されているのかを説明していません。
jamesdlin、2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.