这非常好,称为编译时鸭子类型,并在标准库本身的各种地方使用。说真的,如果不假设模板参数支持某些功能,您将如何使用模板做任何有用的事情?
让我们看一下标准库中的任何算法,例如std::copy
:
template<class InIt, class OutIt>
OutIt copy(InIt first, Init last, OutIt out){
for(; first != last; ++first)
*out++ = *first;
return out;
}
这里,InIt
假设一个类型的对象支持operator*()
(用于间接)和operator++()
推进迭代器。对于 类型的对象,OutIt
假设它也支持operator*()
,并且operator++(int)
. 一个普遍的假设也是,从任何收益中返回的任何东西都是*out++
可分配的(也就是可转换的) 。*first
另一个假设是InIt
和OutIt
都是可复制构造的。
另一个使用它的地方是在任何标准容器中。在 C++11 中,当您使用std::vector<T>
,时,当且仅当您使用任何需要副本的成员函数时,才T
需要是可复制构造的。
所有这些都使得用户定义的类型可以被视为与内置类型相同,即它们是该语言的一等公民。让我们再看一些算法,即那些采用要应用于范围的回调的算法:
template<class InIt, class UnaryFunction>
InIt for_each(InIt first, InIt last, UnaryFunction f){
for(; first != last; ++first)
f(*first);
return first;
}
InIt
假设再次支持与copy
上面示例中相同的操作。但是,现在我们也有UnaryFunction
. 假定这种类型的对象支持后缀函数调用表示法,特别是带有一个参数(一元)。进一步假设这个函数调用的参数可以从任何*first
产量转换。
使用该算法的典型示例是使用普通函数:
void print(int i){ std::cout << i << " "; }
int main(){
std::vector<int> v(5); // 5 ints
for(unsigned i=0; i < v.size(); ++i)
v[i] = i;
std::for_each(v.begin(), v.end(), print); // prints '0 1 2 3 4 '
}
但是,您也可以为此使用函数对象- 一种重载的用户定义类型operator()
:
template<class T>
struct generate_from{
generate_from(T first) : _acc(first) {}
T _acc;
void operator()(T& val){ val = _acc++; }
};
int main(){
std::vector<int> v(5); // 5 ints
// yes, there is std::iota. shush, you.
std::for_each(v.begin(), v.end(), generate_from<int>(0)); // fills 'v' with [0..4]
std::for_each(v.begin(), v.end(), print); // prints '0 1 2 3 4 '
}
如您所见,我的用户定义类型generate_from
可以完全像函数一样对待,它可以像函数一样被调用。请注意,我对T
in做了几个假设generate_from
,即它需要是:
- 可复制构造(在 ctor 中)
- 后增量(在 中
operator()
)
- 可复制分配(在 中
operator()
)