GCC の問題:テンプレート引数に依存する基本クラスのメンバーを使用する
-
08-06-2019 - |
質問
次のコードは gcc ではコンパイルできませんが、Visual Studio ではコンパイルできます。
template <typename T> class A {
public:
T foo;
};
template <typename T> class B: public A <T> {
public:
void bar() { cout << foo << endl; }
};
エラーが発生します:
テスト.cpp:メンバー関数「void B::bar()」内:
テスト.cpp:11:エラー:「foo」はこのスコープで宣言されていませんでした
しかし、そうあるべきです!私が変わったら bar
に
void bar() { cout << this->foo << endl; }
それから する コンパイルしますが、これを行う必要はないと思います。C++ の公式仕様の中に GCC が準拠している何かがあるのでしょうか、それとも単なる癖なのでしょうか?
解決
これは gcc-3.4. 。このリリースでは、C++ パーサーは仕様に従ってさらに厳密になりましたが、それでもレガシーまたはマルチプラットフォームのコード ベースを使用する人々にとっては少々煩わしいものです。
他のヒント
デビッド・ジョイナーには歴史がありました、これがその理由です。
コンパイル時の問題 B<T>
それはその基本クラスですか A<T>
はテンプレート クラスであるため、コンパイラには不明であるため、コンパイラは基本クラスのメンバーを知る方法がありません。
以前のバージョンでは、基本テンプレート クラスを実際に解析することで推論を行っていましたが、ISO C++ では、この推論により、競合が発生すべきではない場所で競合が発生する可能性があると記載されています。
テンプレートで基本クラスのメンバーを参照する解決策は、次のとおりです。 this
(あなたがしたように)または、基本クラスに具体的に名前を付けます。
template <typename T> class A {
public:
T foo;
};
template <typename T> class B: public A <T> {
public:
void bar() { cout << A<T>::foo << endl; }
};
詳細については、 gccマニュアル.
おお。C++ はその奇妙さで私を驚かせ続けます。
テンプレート定義では、非修飾名によって依存ベースのメンバーが検索されなくなります (C++ 標準の [temp.dep]/3 で指定されているように)。例えば、
template <typename T> struct B {
int m;
int n;
int f ();
int g ();
};
int n;
int g ();
template <typename T> struct C : B<T> {
void h ()
{
m = 0; // error
f (); // error
n = 0; // ::n is modified
g (); // ::g is called
}
};
名前を依存させる必要があります。this-> を接頭辞として付けます。C::h の修正された定義は次のとおりです。
template <typename T> void C<T>::h ()
{
this->m = 0;
this->f ();
this->n = 0
this->g ();
}
別の解決策 (残念ながら GCC 3.3 との下位互換性はありません) として、 this-> の代わりに using 宣言を使用することもできます。
template <typename T> struct C : B<T> {
using B<T>::m;
using B<T>::f;
using B<T>::n;
using B<T>::g;
void h ()
{
m = 0;
f ();
n = 0;
g ();
}
};
それはまさにあらゆる種類のクレイジーです。ありがとう、デイビッド。
彼らが参照している標準 [ISO/IEC 14882:2003] の「temp.dep/3」セクションは次のとおりです。
クラス テンプレートまたはクラス テンプレートのメンバーの定義で、クラス テンプレートの基本クラスがテンプレート パラメーターに依存する場合、クラスの定義の時点でも、非修飾名の検索中にも基本クラスのスコープは検査されません。テンプレートまたはメンバー、またはクラス テンプレートまたはメンバーのインスタンス化中。[例:
typedef double A;
template<class T> class B {
typedef int A;
};
template<class T> struct X : B<T> {
A a; // a has typedouble
};
型名
A
の定義ではX<T>
基本クラスで定義された typedef 名ではなく、グローバル名前空間スコープで定義された typedef 名にバインドしますB<T>
. 。] [例:
struct A {
struct B { /* ... */ };
int a;
int Y;
};
int a;
template<class T> struct Y : T {
struct B { /* ... */ };
B b; //The B defined in Y
void f(int i) { a = i; } // ::a
Y* p; // Y<T>
};
Y<A> ya;
メンバー
A::B
,A::a
, 、 そしてA::Y
テンプレート引数のA
の名前のバインドには影響しませんY<A>
. ]
C++ がここで何も想定できない主な理由は、基本テンプレートを後で型に特化できるためです。元の例の続き:
template<>
class A<int> {};
B<int> x;
x.bar();//this will fail because there is no member foo in A<int>
VC は 2 フェーズ ルックアップを実装していませんが、GCC は実装しています。そのため、GCC はインスタンス化される前にテンプレートを解析するため、VC よりも多くのエラーを検出します。あなたの例では、 foo は「T」に依存するため、依存名です。コンパイラにテンプレートの出所を伝えない限り、テンプレートをインスタンス化する前にテンプレートの有効性をまったくチェックできません。そのため、それがどこから来たのかをコンパイラーに伝える必要があります。