(私のC ++ 11の答えについてはこちらも参照してください)
C ++プログラムを解析するために、コンパイラーは特定の名前が型であるかどうかを知る必要があります。次の例は、そのことを示しています。
t * f;
これはどのように解析する必要がありますか?多くの言語では、コンパイラーは解析するために名前の意味を知る必要がなく、基本的にコード行が実行するアクションを知る必要はありません。ただし、C ++では、上記のt
意味がどうなるかによって、解釈が大きく異なります。型の場合は、ポインタの宣言になりますf
。ただし、それが型でない場合は、乗算になります。したがって、C ++標準は段落(3/7)で次のように述べています。
一部の名前はタイプまたはテンプレートを示します。一般に、名前に遭遇するときはいつでも、その名前を含むプログラムの解析を続ける前に、その名前がこれらのエンティティの1つを示しているかどうかを判別する必要があります。これを決定するプロセスは、名前検索と呼ばれます。
テンプレート型パラメーターを参照しているt::x
場合、コンパイラーは名前が何を参照しているかをどのようにして見つけt
ますか?x
乗算が可能な静的intデータメンバーであるか、同様に、宣言を生成できる入れ子になったクラスまたはtypedefである可能性があります。名前にこのプロパティがある場合-実際のテンプレート引数がわかるまで検索できない-それは依存名と呼ばれます(テンプレートパラメーターに「依存」します)。
ユーザーがテンプレートをインスタンス化するまで待つことをお勧めします。
ユーザーがテンプレートをインスタンス化するまで待って、後での本当の意味を見つけましょうt::x * f;
。
これは機能し、実際には可能な実装アプローチとして標準で許可されています。これらのコンパイラーは基本的にテンプレートのテキストを内部バッファーにコピーし、インスタンス化が必要な場合にのみ、テンプレートを解析して、定義のエラーを検出します。ただし、他の実装では、テンプレートの作成者が作成したエラーでテンプレートのユーザー(同僚が貧しい人!)を煩わせるのではなく、インスタンス化が行われる前に、できるだけ早くテンプレートをチェックして定義にエラーを与えることを選択します。
したがって、特定の名前はタイプであり、特定の名前はタイプではないことをコンパイラーに伝える方法が必要です。
「typename」キーワード
答えは、コンパイラがこれをどのように解析するかを決定することです。t::x
が依存名である場合typename
、コンパイラに特定の方法で解析するように指示するために、プレフィックスを付ける必要があります。標準は(14.6 / 2)で述べています:
テンプレートの宣言または定義で使用され、template-parameterに依存する名前は、適切な名前の検索でタイプ名が見つからないか、名前がキーワード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
andの任意の定義を想像してください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
テンプレート名は、aの後だけでなく::
、a ->
または.
クラスメンバーアクセスの後にも出現できます。そこにもキーワードを挿入する必要があります:
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 +
and operator 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
{ };
宣言の使用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
};