233

以下のクラスがテンプレートでない場合は、クラスに含めることができ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;
}
4

3 に答える 3

321

簡単な答え: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がtheの場合、上記のコードは形式が正しくないため、テンプレートではなく単純な関数でintある場合と同じように、診断する必要があります。foo

現在、標準では、テンプレートパラメータに依存しない名前は、フェーズ1で解決可能である必要があるとされています。Aここでは、依存する名前ではなく、タイプに関係なく同じものを参照しTます。したがって、フェーズ1で見つけてチェックするには、テンプレートを定義する前に定義する必要があります。

T::ATに依存する名前になります。フェーズ1では、それがタイプであるかどうかを知ることはできません。インスタンス化のように最終的に使用されるタイプTはまだ定義されていない可能性が高く、たとえ定義されていたとしても、どのタイプがテンプレートパラメーターとして使用されるかはわかりません。ただし、不正なテンプレートの貴重なフェーズ1チェックを実行するには、文法を解決する必要があります。したがって、標準には従属名の規則があります。コンパイラは、それらがtypename型であることを指定する資格がない限り、または特定の明確なコンテキストで使用されない限り、それらが非型であると想定する必要があります。たとえば、では、は基本クラスとして使用されるため、明確に型になります。データメンバーを持つタイプでインスタンス化された場合template <typename T> struct Foo : T::A {};T::AFooAネストされたタイプ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の特殊化を定義してからインスタンス化する可能性があることです。 。したがって、そのインスタンス化では、テンプレートはグローバルを返す代わりにデータメンバーを返します。または逆に、の基本テンプレート定義が持っていた場合、それなしで特殊化を定義でき、テンプレートはで返されるグローバルを探します。これはあなたが抱えている問題と同じくらい驚くべき苦痛であると判断されたと思いますが、それは静かにですBarBazBar<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;あいまいですが、意味があります)。繰り返しになりますが、ルールがどのように合意されたかはよくわかりません。私の推測では、必要となるテキストのページ数は、コンテキストがタイプを取り、タイプを持たない特定のルールを多数作成することに対して軽減されます。

于 2011-01-10T02:44:55.327 に答える
15

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

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


更新:mmichaelのコメントに応えて、C ++ 11標準のドラフトN3337から:

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

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

于 2011-01-10T01:48:04.480 に答える
12

継承中xは非表示になります。次の方法で再表示できます。

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

public:
    using base<T>::x;             // added "using" statement
    int f() { return x; }
};
于 2011-01-10T01:47:52.123 に答える