2 番目のサンプル コードはコンパイルされません。これは、コンパイル時のオーバーロード解決と、実行するコードを「選択」するための実行時の条件分岐の違いの症状です。
- 「取る関数
true_type
またはパラメーターをオーバーロードする」により、決定が型とコンパイル時の定数のみに依存する場合、コンパイル時false_type
にこの選択を行うことができます。
- いくつかの変数値がわかっている実行時
if
まで選択を行うことができない場合は、「チェックの使用」が必要です。
あなたの例でbool match = my_list.contains(my_value)
は、プログラムを実行する前に明らかにわからないため、オーバーロードを使用できません。
しかし違いはテンプレートにとって最も重要であり、選択は「実行するパス」だけでなく、「インスタンス化してコンパイルしてから呼び出すコード」です。リンクされたビデオのコードは、むしろこの精神に基づいています。
この(間違った)コードを考えてみましょう(簡潔にするために#include
s とstd::
s を省略しています):
template<typename InIt>
typename iterator_traits<InIt>::difference_type
distance(InIt first, InIt last)
{
// Make code shorter
typedef typename iterator_traits<InIt>::difference_type Diff;
typedef typename iterator_traits<InIt>::iterator_category Tag;
// Choice
if (is_same<Tag, random_access_iterator_tag>::value)
{
return last - first;
}
else
{
Diff n = 0;
while (first != last) {
++first;
++n;
}
return n;
}
}
ここには少なくとも 2 つの問題があります。
- ランダム アクセスではないイテレータ (例: ) で呼び出そうとすると
std::list<T>::iterator
、実際にはコンパイルに失敗します(行を指すエラーが発生しますreturn last - first;
)。コンパイラは、分岐と分岐の両方を含む関数本体全体をインスタンス化してコンパイルする必要があり (実行されるのは 1 つだけですが)、式は RA 以外の反復子に対しては無効です。if
else
last - first
- それがコンパイルされたとしても、コンパイル時にすぐにテストできた条件について実行時に(関連するオーバーヘッドを使用して)テストを実行し、不要なコード部分をコンパイルすることになります。(コンパイラはそれを最適化できるかもしれませんが、それがコンセプトです。)
それを修正するには、次のことができます。
// (assume needed declarations...)
template<typename InIt>
typename iterator_traits<InIt>::difference_type
distance(InIt first, InIt last)
{
// Make code shorter
typedef typename iterator_traits<InIt>::iterator_category Tag;
// Choice
return distanceImpl(first, last, is_same<Tag, random_access_iterator_tag>());
}
template<typename InIt>
typename iterator_traits<InIt>::difference_type
distanceImpl(InIt first, InIt last, true_type)
{
return last - first;
}
template<typename InIt>
typename iterator_traits<InIt>::difference_type
distanceImpl(InIt first, InIt last, false_type)
{
// Make code shorter
typedef typename iterator_traits<InIt>::difference_type Diff;
Diff n = 0;
while (first != last) {
++first;
++n;
}
return n;
}
または代わりに (ここで可能) 型を直接:
/* snip */
distance(InIt first, InIt last)
{
/* snip */
return distanceImpl(first, last, Tag());
}
/* snip */
distanceImpl(InIt first, InIt last, random_access_iterator_tag)
{
return last - first;
}
/* snip */
distanceImpl(InIt first, InIt last, input_iterator_tag)
{
/* snip */
Diff n = 0;
/* snip */
return n;
}
これで、「正しい」ものだけdistanceImpl
がインスタンス化されて呼び出されます (選択はコンパイル時に行われます)。
これが機能するのは、型 (InIt
または などTag
) がコンパイル時にis_same<Tag, random_access_iterator_tag>::value
既知であり、コンパイル時にも既知の定数であるためです。コンパイラは、型に基づいてのみ、呼び出されるオーバーロードを解決できます (オーバーロードの解決)。
注: 「タグ」は値で渡されますが、「ディスパッチ」の名前のない未使用のパラメーターとしてのみ使用され (値は使用されず、型のみが使用されます)、コンパイラーはそれらを最適化できます。
また、Scott Meyers の「Effective C++, Third Edition 」から、Item 47: Use traits classes を読んで型に関する情報を得ることができます。