短い答え: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がの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で使用されるすべての型(組み込み型を除く)を修飾する必要があります。Fooの内部では、次のように記述する必要があります。
typename std::string s = "hello, world";
なぜなら std::string
依存名で、従って、特に断りのない限り、非タイプであると仮定なります。痛い!
優先コード(return x;
)を許可することの2番目の問題は、Bar
以前Foo
に定義されてx
いて、その定義のメンバーではない場合でも、誰かが後でデータメンバーを持つような特定のBar
型の特殊化を定義し、インスタンス化できることです。したがって、そのインスタンス化では、テンプレートはグローバルを返す代わりにデータメンバーを返します。または逆に、の基本テンプレート定義がだった場合、特殊化はそれなしで定義でき、テンプレートは、を返すグローバルを探しBaz
Bar<Baz>
x
Foo<Baz>
x
Bar
x
x
Foo<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;
があいまいですが)。繰り返しになりますが、ルールがどのように合意されたかはよくわかりません。私の推測では、必要なテキストのページ数は、どのコンテキストがタイプを取り、どのタイプがタイプではないかについて、多くの特定のルールを作成することに対して軽減されました。