您的第二个示例代码无法编译,这是编译时重载决议和运行时条件分支“选择”要执行的代码之间差异的症状。
- 如果决定仅取决于类型和编译时常量,则“重载要获取
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;
}
}
这里至少有两个问题:
- 如果您尝试使用非随机访问的迭代器(例如
std::list<T>::iterator
)调用它,它实际上将无法编译(错误指向行return last - first;
)。编译器必须实例化和编译整个函数体,包括分支和if
分支else
(即使只执行一个),并且表达式last - first
对于非 RA 迭代器无效。
- 即使已编译,我们也会在运行时(具有相关开销)针对我们可以在编译时立即测试的条件进行测试,并编译不需要的代码部分。(编译器可能能够优化它,但这就是概念。)
要修复它,您可以执行以下操作:
// (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
or Tag
)在编译时是已知的,并且is_same<Tag, random_access_iterator_tag>::value
也是在编译时已知的常量。编译器只能基于类型(即重载解析)来解析要调用的重载。
注意:尽管“标签”是按值传递的,但它们仅用作“调度”的未命名、未使用的参数(不使用它们的值,仅使用它们的类型),编译器可以优化它们。
您还可以阅读第 47 条:使用特征类以获取有关Scott Meyers 的Effective C++, Third Edition中的类型的信息。