[解決済み】なぜthisポインタを介してテンプレートベースクラスのメンバにアクセスしなければならないのでしょうか?
質問
もし、以下のクラスがテンプレートでなければ、単純に以下のようにすることができます。
x
の中に
derived
クラスがあります。しかし、以下のようなコードで、私は
が必要です。
使用
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;
}
解決方法は?
簡単に言うと
x
は従属名であり、テンプレート・パラメータが判明するまで検索は延期されます。
長い答え:コンパイラはテンプレートを見たとき、テンプレート・パラメータを見ずに特定のチェックを即座に実行することになっています。その他のチェックは、パラメータがわかるまで延期されます。これは二相コンパイルと呼ばれ、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 がオブジェクトの場合、それは乗算です(そして、何らかの演算子のオーバーロードを除けば、rvalue に代入することは違法です)。もしそれが間違っていれば、このエラーは診断されなければなりません。
フェーズ1
標準ではエラーと定義されています。
テンプレート内の
特定のインスタンス化ではなく、そのインスタンス化です。たとえテンプレートが一度もインスタンス化されなかったとしても,A が
int
の場合、上記のコードは不正な形式であり、診断されなければなりません。
foo
はテンプレートではなく、単なる関数です。
さて、この規格では
ではない
テンプレートパラメータに依存するものは、フェーズ1で解決可能でなければなりません。
A
は従属名ではなく、型に関係なく同じものを指しています。
T
. したがって、フェーズ1で検出およびチェックするためには、テンプレートが定義される前に定義される必要があります。
T::A
はTに依存する名前になります。それが型であるかどうかは、フェーズ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の内部では、こう書かなければならないだろう。
typename std::string s = "hello, world";
なぜなら
std::string
は従属名であるため、特に指定がない限り非型であるとみなされます。痛っ!
好みのコードを許可することの第二の問題点 (
return x;
) は、たとえ
Bar
の前に定義されている
Foo
であり、かつ
x
がその定義のメンバでない場合、誰かが後で
Bar
ある型に対して
Baz
というような
Bar<Baz>
はデータメンバ
x
をインスタンス化し、さらに
Foo<Baz>
. したがって、そのインスタンス化において、テンプレートは、グローバルな
x
. また逆に、ベースとなるテンプレートの定義が
Bar
がありました。
x
を定義することができ、テンプレートはグローバルな
x
で返すようにします。
Foo<Baz>
. これは、あなたの抱えている問題と同じように驚き、悩むと判断したのだと思いますが、それは
静かに
驚くようなエラーを投げるのとは対照的です。
このような問題を避けるために、この規格では、クラス・テンプレートの従属基底クラスは、明示的に要求されない限り、検索対象として考慮されないと言うことにしています。これによって、依存する基底クラスで見つかるかもしれないという理由だけで、すべてが依存することを止めます。また、あなたが見ているような好ましくない効果もあります。つまり、ベースクラスから何かを認定しなければ、それが見つからないということです。を作るには、3つの一般的な方法があります。
A
に依存します。
-
using Bar<T>::A;
をクラスで使用します。A
にあるものを参照しています。Bar<T>
従って、依存する。 -
Bar<T>::A *x = 0;
at point of use - もう一度。A
は間違いなくBar<T>
. これは乗算です。typename
が使われていないので、もしかしたら悪い例かもしれませんが、インスタンス化するまではoperator*(Bar<T>::A, x)
は rvalue を返します。もしかしたら、そうかもしれません。 -
this->A;
at point of use - (使用時A
はメンバーなので、もしそれがFoo
標準では、これは依存関係にあるとされています。
二相コンパイルは厄介で難しく、コードに余分な文言が必要になることもあります。しかし、むしろ民主主義のように、他のすべての方法を除けば、おそらく最悪の方法なのです。
あなたの例では、合理的に主張することができます。
return x;
は意味をなさない。
x
は基底クラスのネストした型なので、言語は (a) 従属名であると言い、 (2) 非型として扱うべきであり、あなたのコードは
this->
. しかし、ベースクラスがグローバルのシャドウとなる名前を導入する可能性がある、あるいは、持っていると思っていた名前を持たず、代わりにグローバルが見つかるという問題が残っているのです。
また、依存する名前については、デフォルトはその逆であるべきだと主張することもできます(何らかの方法でオブジェクトであると指定されない限り、型を仮定する)、またはデフォルトはより文脈に敏感であるべきだと主張することもできます(例えば
std::string s = "";
,
std::string
は、文法的に意味をなさないので、型として読むことができます。
std::string *s = 0;
は曖昧である)。繰り返しになりますが、このルールがどのように合意されたのか、私はよく知りません。私の推測では、どの文脈が型をとり、どの文脈が型でないかという具体的なルールをたくさん作ると、テキストのページ数が増えてしまうので、それを軽減するためではないかと思います。
関連
-
[解決済み】C++エラー:の初期化に一致するコンストラクタがありません。
-
[解決済み】エラー:free(): 次のサイズが無効です(fast)。
-
[解決済み】std::cin.getline( ) vs. std::cin
-
[解決済み] なぜ、オブジェクトそのものではなく、ポインタを使用しなければならないのですか?
-
[解決済み] なぜList<T>を継承しないのですか?
-
[解決済み] template "と "typename "キーワードはどこに、なぜ入れなければならないのですか?
-
[解決済み] ベースクラスのコンストラクタを呼び出す際のルールは?
-
[解決済み】画像処理。コカ・コーラ缶」認識のためのアルゴリズム改良
-
[解決済み] Intel CPU の _mm_popcnt_u64 で、32 ビットのループカウンターを 64 ビットに置き換えると、パフォーマンスが著しく低下します。
-
[解決済み】派生クラスでオーバーライドされた関数が、ベースクラスの他のオーバーロードを隠してしまうのはなぜですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】識別子 "string "は未定義?
-
[解決済み】テンプレートの引数1が無効です(Code::Blocks Win Vista) - テンプレートは使いません。
-
[解決済み】C++エラー:の初期化に一致するコンストラクタがありません。
-
[解決済み】浮動小数点例外エラーが発生する: 8
-
[解決済み] 非静的データメンバの無効な使用
-
[解決済み】なぜ、サイズ8の初期化されていない値を使用するのでしょうか?
-
[解決済み】 while(cin) と while(cin >> num) の違いは何ですか?)
-
[解決済み】Visual Studioのデバッガーエラー。プログラムを開始できません 指定されたファイルが見つかりません
-
[解決済み】デバッグアサーションに失敗しました
-
[解決済み] スタックアロケーションにより初期化されていない値が作成された