C++キーワードtypenameの深い理解


問題:次のtemplate declarations(テンプレート宣言)でclassとtypenameはどう違いますか?

template<class T> class Widget; // uses "class"
template<typename T> class Widget; // uses "typename"
答え:違いはありません。template type parameter(テンプレートタイプパラメータ)を宣言するとき、クラスとtypenameは全く同じものを意味します。いくつかのプログラマはすべての時間でクラスを使うのが好きです。入力しやすいからです。他の人(私を含めて)typenameのほうが好きです。このパラメータはクラスタイプではないことを暗示しています。少数の開発者はどのタイプでも許可された時にtypenameを使いますが、classをuser-defined types(ユーザー定義タイプ)のみを受け入れる場合に保留します。しかし、C++の観点から、クラスとtypenameはtemplate parameter(テンプレートパラメータ)を宣言する時、全く同じものを意味します。しかし、C++はいつもclassとtypenameを同じものとして扱うわけではない。時々あなたはtypenameを使わなければなりません。この点を理解するために、あなたが一つのtemplate(テンプレート)に関与する二つの名前を検討しなければなりません。STL-comptible container(STL互換容器)に持っている割り当て可能なオブジェクトを取得できる関数のテンプレートがあると仮定します。さらにこの関数は、その第二の要素の値を単純に印刷するだけであると仮定する。これは愚かな方法で実現された愚かな関数で、しかも私の下に書いたように、コンパイルすらできません。でも、これらを置いてください。私の愚かさを発見できる方法があります。

template<typename C> // print 2nd element in
void print2nd(const C& container) // container;
{
// this is not valid C++!
if (container.size() >= 2) {
C::const_iterator iter(container.begin()); // get iterator to 1st element
++iter; // move iter to 2nd element
int value = *iter; // copy that element to an int
std::cout << value; // print the int
}
}
はこの関数の中の二つのlocal variabeles(局所変数)、iterとvalueを強調しました。iterのタイプはC:const_iteratorは、template parameter(テンプレートパラメータ)Cに依存するタイプです。一つのtemplateの中の一つのtemplate parameterに依存する名前はdependent namesと呼ばれます。一つのdependent names(名前依存)が一つのクラスの内部に組み込まれている時、私はそれをneted dependent nameと呼びます。C:const_iteratorはneted dependent nameです。実際には、neted dependent type name(ネスト依存タイプ名)であり、つまり、type(タイプ)に関するneted dependent name(ネスト依存名前)である。print 2 ndのもう一つのlocal variableはintタイプを持っています。intは、template parameter(テンプレートパラメータ)に依存しない名前です。この名前はnon-dependent names(名前に依存しない)で有名です。なぜ彼らはindependent namesと呼ばないのか理解できません。もし、私のように「non-dependent」という用語が嫌なものだと気づいたら、あなたは私と共鳴しましたが、「non-dependent」はこのような名前の用語です。だから、私のように、目を回してあなたの自己主張を放棄します。)neted dependent name(入れ子依存名前)は解析が困難になります。例えば、私たちがもっと愚かにこの方法でprint 2 nd:

template<typename C>
void print2nd(const C& container)
{
C::const_iterator * x;
...
}
を開始すると仮定すると、これは私たちがxを指しているように見える。iteratorのlocal variable(局所変数)。しかし、それは見ただけで、私たちはC:const_を知っています。iteratorはタイプです。でもC::const_iteratorはタイプではないですか?Cにstatic data memberがあったら、たまたまconst_といいます。iteratorは?またxがたまたまglobal variableの名前だったら?この場合、上のコードはlocal variableではなく、Cになります。iteratorにxをかける!もちろん、それは愚かに聞こえるかもしれませんが、C++解析器を作成する人は可能なすべての入力を考慮しなければなりません。Cが既知になるまでは、Cを知る方法はありません。iteratorはあくまでもタイプではなく、template(テンプレート)print 2 ndが解析されると、Cはまだ知られていません。C++は、この曖昧性を解決するルールがあります。もし解析器がtemplate(テンプレート)でneted dependent name(入れ子依存名前)に出会ったら、その名前はタイプではないと仮定します。他の方法で教えない限り、。デフォルトの場合、neted dependent name(入れ子依存名前)はtypes(タイプ)ではありません。この規則には例外があります。後で教えます。)これを覚えてください。print 2 ndの冒頭を見てください。

template<typename C>
void print2nd(const C& container)
{
if (container.size() >= 2) {
C::const_iterator iter(container.begin()); // this name is assumed to
... // not be a type
これはどうして合法的なC++ではないですか?今はよく分かります。iterのdeclarationはCのみ:const_iteratorはtypeの時にこそ意味がありますが、C++はそれではなく、C++はそれではないと仮定しています。この状況を変えるには、C++C:const_iteratorはタイプです。私たちはtype nameをそのすぐ前に置いてこのようにします。

template<typename C> // this is valid C++
void print2nd(const C& container)
{
if (container.size() >= 2) {
typename C::const_iterator iter(container.begin());
...
}
}
で通用する規則は簡単です。template(テンプレート)のneted dependent typename(ネスト依存タイプ名)に関わるときは、単語typenameをその前にくっつけて置く必要があります。改めて申し上げます。あとで例外を説明します。type nameは、neted dependent type name(ネスト依存タイプ名)を識別するためにのみ使用されるべきである。他の名前は使うべきではないです。例えば、これは一つのcontainer(容器)とこのcontainer(容器)の中の一つのiteratorを取得するfunction template(関数テンプレート)です。

template<typename C> // typename allowed (as is "class")
void f(const C& container, // typename not allowed
typename C::iterator iter); // typename required
Cは一つのnese ted dependent type nameではありません。したがって、containerを宣言するときは、type nameに前置きされる必要はありませんが、C:iteratorは、neted dependent type name(入れ子依存タイプ名)ですので、typenameに前置きされなければなりません。typename must precedependent type namens規則の例外は、typenameが一つのlist of base clases(ベースリスト)に前に置く必要がないか、または一つのmember initialization list(メンバー初スタートリスト)にあるベースクラス識別子としてdent type name(ネスト依存タイプ名)。例えば、

template<typename T>
class Derived: public Base<T>::Nested {
// base class list: typename not
public: // allowed
explicit Derived(int x)
: Base<T>::Nested(x) // base class identifier in mem
{
// init. list: typename not allowed
typename Base<T>::Nested temp; // use of nested dependent type
... // name not in a base class list or
} // as a base class identifier in a
... // mem. init. list: typename required
};
という矛盾は嫌ですが、経験したことがあると、ほとんど気にしません。最後のtypenameの例を見てみましょう。これはあなたが見ているコードの中で代表的なものです。私たちがiteratorを取得するfunction templateを書いていると仮定します。そして私たちはiteratorが指すobjectの局部を作ってtempをコピーします。

template<typename IterT>
void workWithIterator(IterT iter)
{
typename std::iterator_traits<IterT>::value_type temp(*iter);
...
}
はstdを譲らないでください。trits:value_typeはあなたを驚かします。それは単にスタンダード・トラストクラスの使用であり、C++という言い方をすると「the type of thing pointed to by object of type IterT」となります。このステートメントは、IterT objectが指し示すものと同じタイプのlocal variable(局所変数)(temp)を宣言し、さらにiterが指すobjectでtempを初期化しました。IterTがvector:iteratorであれば、tempはintタイプです。IterTがlist:iteratorなら、tempはstringタイプです。std::iterator_trits:value_typeはneted dependent type name(入れ子依存タイプ名)です。typeネストはiterator_にあります。trits内部、そしてIterTはtemplate parameter(テンプレートパラメータ)であり、typenameに前置きさせなければなりません。stdを読むと思ったら:iterator_trits:value_typeが嫌なので、それと同じものを想像して表現します。ほとんどのプログラマのように、何度も入力することに恐怖を感じたら、typedefを作成する必要があります。value_のようにtypeのようなtrits member namesは、一般的にはtypedef nameがtrits member nameと同じであることから、このようなlocal typedefは通常、このように定義されています。

template<typename IterT>
void workWithIterator(IterT iter)
{
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp(*iter);
...
}
多くのプログラマが最初に「typedef typename」を発見したのです。しかし、それはneted dependent type names(ネスト依存型名)規則に関する合理的な付随結果である。かなり速く慣れます。やはりあなたは強い動機を持っています。typename stdを入力してください:iterator_trits:value_typeはどれぐらいかかりますか?終了語としては、コンパイラとコンパイラとの間でtypenameをめぐる規則の実行状況の違いに言及しなければならない。いくつかのコンパイラが必要なtypenameを受け入れる時、コードがありません。いくつかのコンパイラがtypenameを許さないときに存在するコードを受け入れます。まだ少数の(通常は古い)が、typenameが必要な場所に現れることを拒否します。これは、typenameとneted dependent type namesの相互作用が、わずかな移植可能性の問題を引き起こすことを意味する。Things to Remember・template parameters(テンプレートパラメータ)を宣言する場合、クラスとtypenameは互換可能です。・typenameでneted dependent type namesを識別し、base class lists(ベースリスト)または一つのmember initialization list(メンバー初期化リスト)で一つのbase class ideantifer(ベース識別子)として除外する。