読書ノートeffective c++Item 42 typenameの2つの意味を理解する
14551 ワード
1.classとtypenameの意味が同じ例
質問:次のテンプレート宣言でclassとtypenameの違いは何ですか?
答え:何の違いもありません.テンプレートタイプパラメータを宣言すると、classとtypenameは同じことを意味します.一部のプログラマーはclassを使うのが好きです.叩きやすいからです.他の(私を含む)は、パラメータがclassタイプである必要がないことを示すため、typenameを使用するのが好きです.一部のプログラマーは、任意のtypeを使用することを許可するときにtypenameを使用し、ユーザーがカスタマイズしたタイプに対してclassを使用するだけです.しかし、C++の観点から、テンプレートパラメータを宣言するときにclassとtypenameは同じことを意味します.
2.typenameを使用する必要がある例
しかし,C++はclassとtypenameを必ずしも同等に扱うわけではない.typenameを使用する必要がある場合があります.いつ使用しなければならないかを理解するために,テンプレートで参照できる2つの名前について議論しなければならない.
STLと互換性のあるコンテナをテンプレートパラメータとして使用する関数テンプレートがあるとします.このコンテナに含まれるオブジェクトはintタイプに割り当てられます.さらに、この関数は、コンテナ内の2番目の要素値を印刷すると仮定します.私は以下で愚かな方法で愚かな関数を実現しました.それはコンパイルすることもできませんが、これらのことを無視して、次の例を見てください.
この関数の2つのローカル変数をハイライトしました.iterとvalueです.IterのタイプはC::const_テンプレートパラメータCに依存するiterator.テンプレート内のテンプレートパラメータに依存する名前を依存名前(dependent names)と呼ぶ.依存名がクラスにネストされている場合、私はそれを埋め込み依存名(nested dependent name)と呼びます.C::const_iteratorは埋め込み依存名です.実際には、埋め込み依存タイプ名(nested dependent type name)、つまりタイプ(type)を指す埋め込み依存名です.
print 2 ndの他のローカル変数の場合、value、タイプはintです.intはテンプレートパラメータに依存しません.この名前は「非依存名」(non-dependent names)と呼ばれています.(なぜ独立名(independent names)と呼ばないのか分かりません.「non-dependent」は悪い命名方法ですが、用語なのでこの約束を守る必要があります.)
埋め込みは名前に依存して解析が困難になる.たとえばprint 2 nd関数を次のように開始すると、より愚かになります.
ローカル変数xを宣言したように見えます.このxポインタはC::const_を指します.iterator.しかし、それは私たちが「知っている」ためだけに見えます.C::const_iteratorはtypeです.でもC::const_iteratorはtypeではないとどうなりますか?Cに静的データメンバーがconst_と命名されている場合iteratorで何が起こるの?もしxがちょうどグローバル変数の名前だったら?この場合、上記のcodeはローカル変数を宣言しません.C::const_iteratorとxの積!クレイジーに聞こえますが、これは可能です.C++コンパイラを実現する人も、クレイジーに見える例を含むすべての可能な入力を考慮しなければなりません.
Cが確定するまで、C::const_を知ることはできません.iteratorがtypeであるかどうかは,関数テンプレートprint 2 ndが解析されるとCが確認できない.このような曖昧な問題を処理するために、C++には、解析器がテンプレートに埋め込まれた依存名に遭遇した場合、typeとは思わないという準則があります.教えてくれない限り.デフォルトでは、埋め込み依存名はtypesではありません.△このルールには例外があります.後で言及します.
上記のルールを心に刻み、print 2 ndの開始部分をもう一度見てみましょう.
なぜこれが有効なC++ではないのか、今は明らかになるはずです.Iterの宣言はC::const_のみiteratorはtypeの場合に意味がありますが、C++はタイプであることを知らなかったので、C++はタイプではないと仮定しました.このような状況を是正するために、C++C::const_に伝えなければなりません.iteratorはタイプです.typenameをtypeの前に置くと、この目的を達成できます.
このルールは簡単です.テンプレートでは、いつでも埋め込み依存タイプの名前を参照し、名前の前にtypenameを付けなければなりません.△例外もあります.後でお話しします.
typenameは、埋め込み依存タイプの名前を確認するためにのみ使用されるべきです.他の名前にはこの接頭辞を付けるべきではありません.たとえば、次の関数テンプレートでは、2つのパラメータ、1つのコンテナ、および1つのコンテナの反復器を使用します.
Cは埋め込み依存タイプ名ではない(テンプレートパラメータに依存するものに埋め込まれていない)ので、コンテナを宣言するときにtypenameを付けるべきではありませんが、C::iteratorは埋め込み依存タイプ名なのでtypenameを付ける必要があります.
3.例外-typenameが使用できない場所
「typename」は、埋め込み依存タイプ名の前に追加する必要があります.「このルールには例外があります.ベースクラスリストの埋め込み依存タイプ名またはメンバー初期化リストのベースクラス識別子にtypenameを追加できません.たとえば、次のようにします.
このような不一致性はうんざりしますが、少し経験があれば気づきます.
4.最後の例——typenameにtypedefを使う
最後のtypenameの例を見てみましょう.それは、実際のコードで見られるものを表しているからです.反復器パラメータを持つ関数テンプレートを実装していると仮定し、反復器が指すオブジェクトのローカルコピーをしたいと考えています.temp.次のように実現できます.
std::iterator_traits::value_typeはあなたを驚かせた.これは標準プロパティクラス(standard traits class)の使用方法の1つにすぎません.これは「タイプIterTオブジェクトが指すタイプ」のC++実装方法です.この文はローカル変数(temp)を宣言します.は、IterTオブジェクトが指すオブジェクトのタイプと一致し、tempをiterが指すオブジェクトに初期化します.IterTがvector::iteratorの場合、tempはintタイプです.IterTがlist::iteratorの場合、tempはstringタイプです.なぜならstd::iterator_traits::value_typeは埋め込み依存型の名前(iterator_traits内部value_typeは埋め込まれ、IterTはテンプレートパラメータ)であり、typenameを追加する必要があります.
std::iterator_を読むと思うならtraits::value_typeは不快なことで、それを打ち出すとどうなるか想像してみてください.ほとんどのプログラマーのように、この式を何度も入力するのは怖いので、typedefを作成したいと思っています.value_のようにtypeのような特性(traits)メンバー名(特性の情報についてはItem 47)は、typedefの名前と特性メンバーの名前を同じにするのが慣例であるため、このようなローカルtypedefは通常、以下のように定義される.
多くのプログラマーは、「typedef typename」を並べて調和がとれていないように見えますが、組み込み依存型の名前を使用するルールでは論理的な結果になります.すぐに慣れます.結局、あなたは強い駆動力を持っています.typename std::iterator_traits::value_typeを何回入力したいですか.
5.Type-nameの実行はコンパイラによって異なる
終了語として、typenameルールの強制実行はコンパイラによって異なり、一部のコンパイラはtypenameが必要だが実際に入力されていない場合を受け入れることに言及すべきである.一部のコンパイラはtypenameが入力されたが実際には許可されていない場合を受け入れる.また、typenameを入力する必要がある場合にtypenameの入力を拒否するコンパイラもあります.これは、typenameと埋め込み依存タイプ名の相互作用が頭を悩ませる問題を意味します.
6.まとめテンプレートパラメータを宣言すると、classとtypenameは互換性があります. はtypenameを使用して埋め込み依存タイプ名を識別するが、ベースクラスリストまたはメンバー初期化リストのベースクラス識別子を除く.
質問:次のテンプレート宣言でclassとtypenameの違いは何ですか?
1 template<class T> class Widget; // uses “class”
2
3 template class Widget; // uses “typename”
答え:何の違いもありません.テンプレートタイプパラメータを宣言すると、classとtypenameは同じことを意味します.一部のプログラマーはclassを使うのが好きです.叩きやすいからです.他の(私を含む)は、パラメータがclassタイプである必要がないことを示すため、typenameを使用するのが好きです.一部のプログラマーは、任意のtypeを使用することを許可するときにtypenameを使用し、ユーザーがカスタマイズしたタイプに対してclassを使用するだけです.しかし、C++の観点から、テンプレートパラメータを宣言するときにclassとtypenameは同じことを意味します.
2.typenameを使用する必要がある例
しかし,C++はclassとtypenameを必ずしも同等に扱うわけではない.typenameを使用する必要がある場合があります.いつ使用しなければならないかを理解するために,テンプレートで参照できる2つの名前について議論しなければならない.
STLと互換性のあるコンテナをテンプレートパラメータとして使用する関数テンプレートがあるとします.このコンテナに含まれるオブジェクトはintタイプに割り当てられます.さらに、この関数は、コンテナ内の2番目の要素値を印刷すると仮定します.私は以下で愚かな方法で愚かな関数を実現しました.それはコンパイルすることもできませんが、これらのことを無視して、次の例を見てください.
1 template // print 2nd element in
2 void print2nd(const C& container) // container;
3 { // this is not valid C++!
4 if (container.size() >= 2) {
5 C::const_iterator iter(container.begin()); // get iterator to 1st element
6 ++iter; // move iter to 2nd element
7 int value = *iter; // copy that element to an int
8
9 std::cout << value; // print the int
10
11 }
12
13 }
この関数の2つのローカル変数をハイライトしました.iterとvalueです.IterのタイプはC::const_テンプレートパラメータCに依存するiterator.テンプレート内のテンプレートパラメータに依存する名前を依存名前(dependent names)と呼ぶ.依存名がクラスにネストされている場合、私はそれを埋め込み依存名(nested dependent name)と呼びます.C::const_iteratorは埋め込み依存名です.実際には、埋め込み依存タイプ名(nested dependent type name)、つまりタイプ(type)を指す埋め込み依存名です.
print 2 ndの他のローカル変数の場合、value、タイプはintです.intはテンプレートパラメータに依存しません.この名前は「非依存名」(non-dependent names)と呼ばれています.(なぜ独立名(independent names)と呼ばないのか分かりません.「non-dependent」は悪い命名方法ですが、用語なのでこの約束を守る必要があります.)
埋め込みは名前に依存して解析が困難になる.たとえばprint 2 nd関数を次のように開始すると、より愚かになります.
1 template
2
3 void print2nd(const C& container)
4
5 {
6
7 C::const_iterator * x;
8
9 ...
10
11 }
ローカル変数xを宣言したように見えます.このxポインタはC::const_を指します.iterator.しかし、それは私たちが「知っている」ためだけに見えます.C::const_iteratorはtypeです.でもC::const_iteratorはtypeではないとどうなりますか?Cに静的データメンバーがconst_と命名されている場合iteratorで何が起こるの?もしxがちょうどグローバル変数の名前だったら?この場合、上記のcodeはローカル変数を宣言しません.C::const_iteratorとxの積!クレイジーに聞こえますが、これは可能です.C++コンパイラを実現する人も、クレイジーに見える例を含むすべての可能な入力を考慮しなければなりません.
Cが確定するまで、C::const_を知ることはできません.iteratorがtypeであるかどうかは,関数テンプレートprint 2 ndが解析されるとCが確認できない.このような曖昧な問題を処理するために、C++には、解析器がテンプレートに埋め込まれた依存名に遭遇した場合、typeとは思わないという準則があります.教えてくれない限り.デフォルトでは、埋め込み依存名はtypesではありません.△このルールには例外があります.後で言及します.
上記のルールを心に刻み、print 2 ndの開始部分をもう一度見てみましょう.
1 template
2 void print2nd(const C& container)
3 {
4 if (container.size() >= 2) {
5 C::const_iterator iter(container.begin()); // this name is assumed to
6 ... // not be a type
なぜこれが有効なC++ではないのか、今は明らかになるはずです.Iterの宣言はC::const_のみiteratorはtypeの場合に意味がありますが、C++はタイプであることを知らなかったので、C++はタイプではないと仮定しました.このような状況を是正するために、C++C::const_に伝えなければなりません.iteratorはタイプです.typenameをtypeの前に置くと、この目的を達成できます.
1 template // this is valid C++
2
3 void print2nd(const C& container)
4
5 {
6
7 if (container.size() >= 2) {
8
9 typename C::const_iterator iter(container.begin());
10
11 ...
12
13 }
14
15 }
このルールは簡単です.テンプレートでは、いつでも埋め込み依存タイプの名前を参照し、名前の前にtypenameを付けなければなりません.△例外もあります.後でお話しします.
typenameは、埋め込み依存タイプの名前を確認するためにのみ使用されるべきです.他の名前にはこの接頭辞を付けるべきではありません.たとえば、次の関数テンプレートでは、2つのパラメータ、1つのコンテナ、および1つのコンテナの反復器を使用します.
1 template // typename allowed (as is “class”)
2 void f(const C& container, // typename not allowed
3 typename C::iterator iter); // typename required
Cは埋め込み依存タイプ名ではない(テンプレートパラメータに依存するものに埋め込まれていない)ので、コンテナを宣言するときにtypenameを付けるべきではありませんが、C::iteratorは埋め込み依存タイプ名なのでtypenameを付ける必要があります.
3.例外-typenameが使用できない場所
「typename」は、埋め込み依存タイプ名の前に追加する必要があります.「このルールには例外があります.ベースクラスリストの埋め込み依存タイプ名またはメンバー初期化リストのベースクラス識別子にtypenameを追加できません.たとえば、次のようにします.
1 template
2 class Derived: public Base::Nested { // base class list: typename not
3
4 public: // allowed
5
6 explicit Derived(int x)
7
8
9
10 : Base::Nested(x) // base class identifier in mem.
11
12 { // init. list: typename not allowed
13
14
15 typename Base::Nested temp; // use of nested dependent type
16 ... // name not in a base class list or
17 } // as a base class identifier in a
18 ... // mem. init. list: typename
19 required
20 };
このような不一致性はうんざりしますが、少し経験があれば気づきます.
4.最後の例——typenameにtypedefを使う
最後のtypenameの例を見てみましょう.それは、実際のコードで見られるものを表しているからです.反復器パラメータを持つ関数テンプレートを実装していると仮定し、反復器が指すオブジェクトのローカルコピーをしたいと考えています.temp.次のように実現できます.
1 template
2 void workWithIterator(IterT iter)
3 {
4 typename std::iterator_traits::value_type temp(*iter);
5 ...
6 }
std::iterator_traits::value_typeはあなたを驚かせた.これは標準プロパティクラス(standard traits class)の使用方法の1つにすぎません.これは「タイプIterTオブジェクトが指すタイプ」のC++実装方法です.この文はローカル変数(temp)を宣言します.は、IterTオブジェクトが指すオブジェクトのタイプと一致し、tempをiterが指すオブジェクトに初期化します.IterTがvector::iteratorの場合、tempはintタイプです.IterTがlist::iteratorの場合、tempはstringタイプです.なぜならstd::iterator_traits::value_typeは埋め込み依存型の名前(iterator_traits内部value_typeは埋め込まれ、IterTはテンプレートパラメータ)であり、typenameを追加する必要があります.
std::iterator_を読むと思うならtraits::value_typeは不快なことで、それを打ち出すとどうなるか想像してみてください.ほとんどのプログラマーのように、この式を何度も入力するのは怖いので、typedefを作成したいと思っています.value_のようにtypeのような特性(traits)メンバー名(特性の情報についてはItem 47)は、typedefの名前と特性メンバーの名前を同じにするのが慣例であるため、このようなローカルtypedefは通常、以下のように定義される.
1 template
2 void workWithIterator(IterT iter)
3 {
4 typedef typename std::iterator_traits::value_type value_type;
5 value_type temp(*iter);
6 ...
7 }
多くのプログラマーは、「typedef typename」を並べて調和がとれていないように見えますが、組み込み依存型の名前を使用するルールでは論理的な結果になります.すぐに慣れます.結局、あなたは強い駆動力を持っています.typename std::iterator_traits::value_typeを何回入力したいですか.
5.Type-nameの実行はコンパイラによって異なる
終了語として、typenameルールの強制実行はコンパイラによって異なり、一部のコンパイラはtypenameが必要だが実際に入力されていない場合を受け入れることに言及すべきである.一部のコンパイラはtypenameが入力されたが実際には許可されていない場合を受け入れる.また、typenameを入力する必要がある場合にtypenameの入力を拒否するコンパイラもあります.これは、typenameと埋め込み依存タイプ名の相互作用が頭を悩ませる問題を意味します.
6.まとめ