37

次のコードは 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; }
};

エラーが発生します:

test.cpp: メンバー関数 'void B::bar()':

test.cpp:11: エラー: 'foo' はこのスコープで宣言されていません

しかし、そうあるべきです!私がに変更barした場合

void bar() { cout << this->foo << endl; }

その後、コンパイルされますが、これを行う必要はないと思います。GCCがここに従っているC ++の公式仕様に何かありますか、それとも単なる癖ですか?

4

5 に答える 5

34

デビッド・ジョイナーには歴史がありました。ここに理由があります。

コンパイル時の問題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 マニュアルを参照してください。

于 2008-08-14T18:09:55.413 に答える
19

わお。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の定義の型名は、基本クラスで定義された typedef 名ではなく、グローバル名前空間スコープで定義された typedef 名にバインドX<T>されます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::BA::a、およびは、での名前のバインディングには影響しません。]A::YAY<A>

于 2008-08-14T18:05:09.510 に答える
12

これはgcc-3.4で変更されました。そのリリースでは、C++ パーサーがより厳密になりました。仕様によると、レガシーまたはマルチプラットフォームのコード ベースを使用しているユーザーにとっては、依然として厄介な問題です。

于 2008-08-14T17:50:24.750 に答える
8

C++ がここで何も仮定できない主な理由は、基本テンプレートを後で型に特化できるからです。元の例の続き:

template<>
class A<int> {};

B<int> x; 
x.bar();//this will fail because there is no member foo in A<int>
于 2008-08-14T21:06:41.360 に答える
3

VC は 2 フェーズ ルックアップを実装していませんが、GCC は実装しています。そのため、GCC はインスタンス化される前にテンプレートを解析するため、VC よりも多くのエラーを検出します。あなたの例では、「T」に依存しているため、foo は依存名です。どこから来たのかをコンパイラに伝えない限り、テンプレートをインスタンス化する前に、テンプレートの有効性をまったくチェックできません。そのため、コンパイラにそれがどこから来たのかを伝える必要があります。

于 2008-10-17T13:21:45.927 に答える