在Deduce type of template type in C++跟进我自己的问题时,有些人提到通过引用传递iterators
并不是惯用的,而是一个不起作用的特定用例:
template <typename Iter> iterate(Iter &first, Iter &last)
{
// Do something with the iterators
}
iterate(container.begin(), container.end()); // error, binding non-const ref to rvalue!
更深入地发现(至少)另外两个主题涉及前者:
但似乎没有问题/答案是否通过rvalue reference&amp;&amp; (甚至)比按价值传递更好。如在
template <typename Iter> iterate(Iter &&first, Iter &&last)
{
// Do something with the iterators
}
iterate(container.begin(), container.end());
我的代码使用rvalue引用编译并运行良好,因此我对此有了想法。
答案 0 :(得分:5)
首先,像你一样使用单个模板参数意味着如果传递的两个迭代器不具有完全相同的类型,那么模板推导将失败(和相同的值类别,因为这个是一个转发参考)。
例如这段代码:
template <typename Iter>
size_t count(Iter &&first, Iter &&last) { ...... }
// ...
std::string s("hello");
auto s_it = s.begin();
count(s_it, s.end());
实际上无法编译,因为它不知道是否要将Iter
推断为string::iterator&
或string::iterator
。如果要传递一个const_iterator和一个非const迭代器,则会出现类似的问题。
您可以使用两个模板参数来解决此问题。但后来我们开始遇到逻辑错误。
迭代器按值传递是惯用的,因此如果迭代器突然通过引用传递,它可能会让人感到惊讶(因此会引入错误)。
例如:
template <typename It1, typename It2>
size_t count(It1 &&first, It2 &&last)
{
size_t c = 0;
while ( first != last )
++first, ++c;
return c;
}
int main()
{
std::string s("hello");
auto s_it = s.begin();
std::cout << count(s_it, s.end()) << ' ';
std::cout << *s_it << '\n';
}
编码器希望在这里输出5 h
,但实际上代码将取消引用结束迭代器,因为你通过引用传递了s_it
并且函数修改了它。
当然,count
可以通过复制first
来避免这种情况......但是现在你浪费了时间和内存,而不是仅仅通过值传递。