4

Following up my own question at Deduce type of template type in C++ some people mentioned passing iterators by reference is not idiomatic and a given use case in which it would not work:

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!

Looking deeper I found (at least) two other topics which cover the former:

But there seems to be no question/answer as to whether passing the iterators by rvalue reference && would be (even) better than passing them by value. As in

template <typename Iter> iterate(Iter &&first, Iter &&last)
{
    // Do something with the iterators
}

iterate(container.begin(), container.end());

My code has compiled and run fine using rvalue references and hence my thoughts about this.

Community
  • 1
  • 1
mementum
  • 3,153
  • 13
  • 20
  • 1
    The rvalue reference in this context is known as forwarding reference, aka universal reference. –  Dec 22 '15 at 07:55
  • 3
    Most iterators are very small (e.g. a single pointer). Passing them by reference doesn't have any (performance) advantages and it makes your code more complex, if you have to accomodate for l- and r-value references. – MikeMB Dec 22 '15 at 07:55

1 Answers1

6

Firstly, using a single template parameter like you did means that template deduction will fail if the two iterators passed don't have exactly the same type, (and the same value category since this is a forwarding reference).

For example this code:

template <typename Iter> 
size_t count(Iter &&first, Iter &&last) { ...... }

// ...

std::string s("hello");
auto s_it = s.begin();
count(s_it, s.end());

actually fails to compile, since it doesn't know whether to deduce Iter to string::iterator& or string::iterator. A similar problem would occur if you wanted to pass one const_iterator and one non-const iterator.


You can fix this by using two template parameters. But then we start to run into logic errors.

It's idiomatic that iterators are passed by value, so it may surprise people (and therefore introduce bugs) if the iterator is suddenly passed by reference.

For example:

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';
}

The coder would expect to output 5 h here, but in fact the code will dereference the end iterator, since you passed s_it by reference and the function modified it.

Of course, count could avoid this by taking a copy of first... but now you have wasted time and memory compared to just passing by value in the first place.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • I think the latter is what I mostly should care about if taking a && reference, because the caller "expects" the iterator not to be modified at all. Which implies that there is nothing "fundamentally" wrong with taking && references, but it may be idiomatically wrong and prone to error by breaking the caller-callee convention – mementum Dec 22 '15 at 08:23
  • @mementum:You should all yourself, why you want to pass an iterator by reference in the first place – MikeMB Dec 22 '15 at 08:38
  • "why not?" could be the answer, But seeing the implications with regards to calling conventions it's clear it will be much better not to. – mementum Dec 22 '15 at 15:51