(私のC ++ 11の回答についてはこちらもご覧ください)
C++ プログラムを解析するために、コンパイラは特定の名前が型であるかどうかを知る必要があります。次の例は、次のことを示しています。
t * f;
これはどのように解析する必要がありますか? 多くの言語では、コンパイラは解析のために名前の意味を知る必要はなく、基本的にコード行が実行するアクションを認識します。ただし、C++ では、上記は意味に応じて大きく異なる解釈をもたらす可能性がありt
ます。型の場合は、ポインタの宣言になりますf
。ただし、型でない場合は乗算になります。したがって、C++ 標準は段落 (3/7) で次のように述べています。
一部の名前は、タイプまたはテンプレートを示します。一般に、名前が検出されるたびに、名前を含むプログラムの解析を続行する前に、その名前がこれらのエンティティのいずれかを示しているかどうかを判断する必要があります。これを決定するプロセスは、名前検索と呼ばれます。
テンプレート型パラメータを参照しているt::x
場合、コンパイラは名前が何を参照しているかをどのように見つけますか? 乗算可能な静的 int データ メンバーである可能性もあれば、宣言を生成できるネストされたクラスまたは typedef である可能性もあります。名前にこのプロパティ (実際のテンプレート引数が判明するまで検索できない) がある場合、その名前は依存名と呼ばれます (テンプレート パラメーターに「依存」します)。t
x
ユーザーがテンプレートをインスタンス化するまで待つことをお勧めします。
ユーザーがテンプレートをインスタンス化するまで待ってから、後で の本当の意味を見つけてt::x * f;
ください。
これは機能し、実際に可能な実装アプローチとして標準で許可されています。これらのコンパイラは、基本的にテンプレートのテキストを内部バッファにコピーし、インスタンス化が必要な場合にのみテンプレートを解析し、定義内のエラーを検出する可能性があります。しかし、テンプレートの作成者によるエラーでテンプレートのユーザー (気の毒な同僚!) を悩ませる代わりに、他の実装では、インスタンス化が行われる前に、早い段階でテンプレートをチェックし、できるだけ早く定義にエラーを与えることを選択します。
したがって、特定の名前は型であり、特定の名前は型ではないことをコンパイラーに伝える方法が必要です。
「typename」キーワード
答えは:コンパイラがこれを解析する方法を決定します。t::x
依存名の場合typename
、特定の方法で解析するようにコンパイラに指示するために、接頭辞 by を付ける必要があります。標準は (14.6/2) で次のように述べています。
テンプレートの宣言または定義で使用され、テンプレート パラメーターに依存する名前は、該当する名前検索で型名が検出されるか、名前がキーワード typename によって修飾されない限り、型に名前を付けないと見なされます。
必要のない名前がたくさんあります。コンパイラは、テンプレート定義で適切な名前の検索を使用して、typename
構成体自体を解析する方法を見つけ出すことができるためです。しかし、が宣言であるためには、 のように書かなければなりません。キーワードを省略し、名前が非型であると見なされた場合、インスタンス化によってそれが型を示していることが判明すると、通常のエラー メッセージがコンパイラによって出力されます。その結果、定義時にエラーが発生する場合があります。T *f;
T
t::x * f;
typename t::x *f;
// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;
構文では、typename
修飾名の前にのみ許可されます。したがって、修飾されていない名前が型を参照する場合、型を参照することが常に知られていることが認められていると見なされます。
紹介テキストで示唆されているように、テンプレートを表す名前にも同様の落とし穴があります。
「テンプレート」キーワード
上記の最初の引用と、標準がテンプレートに対しても特別な処理を必要としている方法を覚えていますか? 次の無害に見える例を見てみましょう。
boost::function< int() > f;
人間の読者には明白に見えるかもしれません。コンパイラの場合はそうではありません。と の次の任意の定義を想像してboost::function
くださいf
。
namespace boost { int function = 0; }
int main() {
int f = 0;
boost::function< int() > f;
}
それは実際には有効な式です。小なり演算子を使用してboost::function
ゼロ ( int()
) と比較し、大なり演算子を使用して結果を と比較bool
しf
ます。ただし、ご存知かもしれませんがboost::function
、実際にはテンプレートであるため、コンパイラーは次のことを認識しています (14.2/3):
名前検索 (3.4) で名前がテンプレート名であることが判明した後、この名前の後に < が続く場合、< は常にテンプレート引数リストの先頭と見なされ、決して名前の後に less- が続くものとして見なされません。オペレーターより。
ここで、 と同じ問題に戻りますtypename
。コードを解析するときに名前がテンプレートかどうかまだわからない場合はどうすればよいでしょうか? template
で指定されているように、テンプレート名の直前に挿入する必要があります14.2/4
。これは次のようになります。
t::template f<int>(); // call a function template
テンプレート名は、クラス メンバー アクセスの後だけでなく、::
後->
または.
クラス メンバー アクセスでも使用できます。そこにもキーワードを挿入する必要があります。
this->template f<int>(); // call a function template
依存関係
分厚い標準の本を棚に置いていて、私が何について話しているのか正確に知りたい人のために、これが標準でどのように指定されているかについて少し話します。
テンプレート宣言では、テンプレートのインスタンス化に使用するテンプレート引数に応じて、いくつかの構成要素の意味が異なります。式は異なる型または値を持つ場合があり、変数は異なる型を持つ場合があるか、関数呼び出しが異なる関数を呼び出すことになる場合があります。このような構成は、一般に、テンプレート パラメーターに依存すると言われています。
標準では、構造が依存しているかどうかによって規則が正確に定義されています。それらを論理的に異なるグループに分けます。1 つは型をキャッチし、もう 1 つは式をキャッチします。式は、その値や型に依存する場合があります。したがって、典型的な例を追加すると、次のようになります。
- 依存型 (例: 型テンプレート パラメーター
T
)
- 値に依存する式 (例: 非型のテンプレート パラメーター
N
)
- 型に依存する式 (例: 型テンプレート パラメーターへのキャスト
(T)0
)
ほとんどのルールは直感的で、再帰的に構築されます。たとえば、値依存式または依存型でT[N]
ある場合、依存型として構築された型です。これの詳細は、依存型、型依存式、および値依存式のセクションで読むことができます。N
T
(14.6.2/1
(14.6.2.2)
(14.6.2.3)
従属名
標準では、従属名が正確に何であるかについて少し不明確です。単純な読み方 (ご存知のように、最小の驚きの原則) では、従属名として定義されているのは、以下の関数名の特殊なケースだけです。しかし、明らかにインスタンス化のコンテキストでも検索する必要があるため、従属名である必要もあります (幸いなことに、C++14 の中頃から、委員会はこの紛らわしい定義を修正する方法を検討し始めています)。T::x
この問題を回避するために、私は標準テキストの単純な解釈に頼っています。依存する型または式を表すすべての構造のうち、それらのサブセットは名前を表します。したがって、これらの名前は「従属名」です。名前はさまざまな形式を取ることができます-標準は次のように述べています:
名前は、エンティティまたはラベル (6.6.4、 6.1)
識別子は単純な文字/数字のシーケンスですが、次の 2 つはoperator +
andoperator type
形式です。最後の形はtemplate-name <argument list>
. これらはすべて名前であり、標準での従来の使用により、名前には名前を検索する名前空間またはクラスを示す修飾子を含めることもできます。
値依存式1 + N
は名前ではなく、名前N
です。名前であるすべての依存構造のサブセットは、依存名と呼ばれます。ただし、関数名は、テンプレートの異なるインスタンス化では異なる意味を持つ場合がありますが、残念ながら、この一般的な規則には当てはまりません。
依存関数名
この記事の主な関心事ではありませんが、言及する価値があります。関数名は個別に処理される例外です。識別子関数名は、それ自体ではなく、呼び出しで使用される型依存の引数式に依存します。例f((T)0)
でf
は、 は従属名です。規格では、 で指定されてい(14.6.2/1)
ます。
追加の注意事項と例
typename
多くの場合、との両方が必要template
です。コードは次のようになります
template <typename T, typename Tail>
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
typedef typename Tail::template inUnion<U> dummy;
};
// ...
};
キーワードtemplate
は、必ずしも名前の最後の部分に表示される必要はありません。次の例のように、スコープとして使用されるクラス名の前に表示できます。
typename t::template iterator<int>::value_type v;
場合によっては、以下に詳述するように、キーワードが禁止されています
依存する基本クラスの名前には、typename
. 指定された名前はクラス型名であると想定されます。これは、基本クラス リストとコンストラクター初期化子リストの両方の名前に当てはまります。
template <typename T>
struct derive_from_Has_type : /* typename */ SomeBase<T>::type
{ };
using 宣言でtemplate
は、最後の の後に使用することはできず::
、C++ 委員会は解決策に取り組まないように言いました。
template <typename T>
struct derive_from_Has_type : SomeBase<T> {
using SomeBase<T>::template type; // error
using typename SomeBase<T>::type; // typename *is* allowed
};