web-dev-qa-db-ja.com

派生テンプレートクラスが基本テンプレートクラスの識別子にアクセスできないのはなぜですか?

考えてみましょう:

template <typename T>
class Base
{
    public:
        static const bool ZEROFILL = true;
        static const bool NO_ZEROFILL = false;
}

template <typename T>
class Derived : public Base<T>
{
    public: 
        Derived( bool initZero = NO_ZEROFILL );    // NO_ZEROFILL is not visible
        ~Derived();
}

これをGCCg ++ 3.4.4(cygwin)でコンパイルできません。

これらをクラステンプレートに変換する前は、それらは非ジェネリックであり、派生クラスは基本クラスの静的メンバーを見ることができました。これはC++仕様の要件での可視性の喪失ですか、それとも私が採用する必要のある構文の変更がありますか?

Base<T>の各インスタンス化には、独自の静的メンバー「ZEROFILL」と「NO_ZEROFILL」があり、Base<float>::ZEROFILLBase<double>::ZEROFILLは異なる変数であることを理解しています。しかし、私は本当に気にしません。コードを読みやすくするために定数があります。静的定数を使用したかったのは、マクロやグローバルよりも名前の競合の点で安全だからです。

43
cheshirekow

それはあなたのための2段階のルックアップです。

Base<T>::NO_ZEROFILL(マクロを除くすべての大文字の識別子はbooです、ところで)はTに依存する識別子です。
コンパイラーが最初にテンプレートを解析するとき、Tの代わりに実際の型がないため、コンパイラーはBase<T>が何であるかを「認識」していません。したがって、定義されていると想定される識別子を知ることはできず(コンパイラが後でしか見ることができない、一部のTsの特殊化がある場合があります)、基本クラスで定義された識別子から基本クラスの修飾を省略することはできません。

そのため、Base<T>::NO_ZEROFILL(またはthis->NO_ZEROFILL)を記述する必要があります。これは、NO_ZEROFILLTに依存する基本クラスの何かであり、後でテンプレートがインスタンス化されたときにのみ検証できることをコンパイラーに通知します。したがって、コードを検証しようとせずにそれを受け入れます。
そのコードは、後でTの実際のパラメーターを指定してテンプレートをインスタンス化したときにのみ確認できます。

46
sbi

発生した問題は、依存する基本クラスの名前検索ルールが原因です。 14.6/8には次のものがあります。

テンプレート定義で使用される名前の宣言を検索する場合、通常のルックアップルール(3.4.1、3.4.2)が非依存名に使用されます。テンプレートパラメータに依存する名前のルックアップは、実際のテンプレート引数がわかるまで延期されます(14.6.2)。

(これは実際には「2フェーズルックアップ」ではありません。その説明については、以下を参照してください。)

14.6/8についてのポイントは、コンパイラに関する限り、例の_NO_ZEROFILL_は識別子であり、テンプレートパラメータに依存しないということです。したがって、3.4.1および3.4​​.2の通常のルールに従って検索されます。

この通常のルックアップは_Base<T>_内を検索しないため、NO_ZEROFILLは単に宣言されていない識別子です。 14.6.2/3には次のものがあります。

クラステンプレートまたはクラステンプレートのメンバーの定義で、クラステンプレートの基本クラスがテンプレートパラメータに依存している場合、クラスの定義の時点でも、非修飾名のルックアップ中に基本クラスのスコープは検査されません。テンプレートまたはメンバー、またはクラステンプレートまたはメンバーのインスタンス化中。

_NO_ZEROFILL_を_Base<T>::_で修飾すると、本質的に非依存名から依存名に変更され、テンプレートがインスタンス化されるまでルックアップが遅延します。

補足:2フェーズルックアップとは:

_void bar (int);

template <typename T>
void foo (T const & t) {
  bar (t);
}


namespace NS
{
  struct A {};
  void bar (A const &);
}


int main ()
{
  NS::A a;
  foo (a);
}
_

上記の例は次のようにコンパイルされます。コンパイラーはfooの関数本体を解析し、依存引数(つまり、テンプレートパラメーターに依存する引数)を持つbarへの呼び出しがあることを確認します。この時点で、コンパイラは3.4.1に従ってバーを検索します。これは、「フェーズ1検索」です。ルックアップは関数void bar (int)を見つけ、それは後でまで依存呼び出しとともに保存されます。

次にテンプレートがインスタンス化されると(mainからの呼び出しの結果として)、コンパイラーは引数のスコープで追加のルックアップを実行します。これが「フェーズ2ルックアップ」です。この場合、結果としてvoid NS::bar(A const &)が見つかります。

コンパイラにはbarに対して2つのオーバーロードがあり、上記の場合はvoid NS::bar(A const &)を呼び出してそれらの中から選択します。

29
Richard Corden

2008年と比べて問題なくコンパイルできるようです。

public:
    Derived( bool initZero = Base<T>::NO_ZEROFILL );
1
Alan

このプログラムをお試しください

_#include<iostream>
using namespace std;
template <class T> class base{
public:
T x;
base(T a){x=a;}
virtual T get(void){return x;}
};
template <class T>
class derived:public base<T>{
public:
derived(T a):base<T>(a){}
T get(void){return this->x+2;}
};
int main(void){
base<int> ob1(10);
cout<<ob1.get()<<endl;
derived<float> ob(10);
cout<<ob.get();
return 0;
}
_

T get(void){return this->x+2;}行で、uはスコープ解決(::)演算子を使用することもできます。たとえば、行を次のように置き換えてみてください

_T get(void){return base<T>::x+2;}
_
0
shahnoor rahman