Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Range TS idioms and the mysterious auto &&

Tags:

c++

c++20

In pre-Range TS code, I might do something like the following to get a (potentially modifiable) reference to a ForwardIterator's value:

auto &val = *it;

This would also be used in range-based for loops over such iterators:

for(auto &val : some_range)

However, in C++20 and Range TS-based code, I'm seeing a lot of auto&& usage in these locations. I understand what auto&& is doing from a language standpoint. What I don't understand is why it's being used in these places, when auto& ought to work just fine? Most code of this nature isn't forwarding the reference, so why is it using a forwarding reference to capture it?

like image 578
Nicol Bolas Avatar asked Jul 22 '18 01:07

Nicol Bolas


1 Answers

This idiom is being used because the Range TS and the C++20-equivalent actually requires this idiom. The reason for this requirement has to do with the fact that proxy iterators are now legal constructs for all categories of iterators.

A proxy iterator's operator* does not return a value_type&; it returns a prvalue of an object that acts like an reference. Therefore, if you tried to store a reference to the value with auto&, your code would fail to compile with a proxy iterator.

The Range TS's iterator concepts never require that the return value from operator* is actually a language reference. The Readable concept only requires that the return value is convertible to a value_type. The Writable concept only requires that a value_type can be assigned to the return value.

So code getting references from iterators of all kinds under the Range feature must now use auto&&, which can bind to prvalues without making them const. So when dealing with ranges, if you want to get a possibly non-const reference to the value, auto&& is the most convenient way. If you want just a const reference, you can continue to use const auto &.


Note that auto&& also handles iterators that directly return prvalues of type value_type from their operator*. Obviously these are only useful for Readable iterators, but they're quite useful. The simplest example would be an iterator for a counting range over some number of integers. The iterator returns an int, not a reference to some memory. Such iterators can be RandomAccessIterators in the Range TS due to not having a hard requirement for returning a language reference.

Such iterators would also make sense for regular expression matching. The current regex_iterator returns a reference to an internal object; that's necessary for being a ForwardIterator. Instead, it could have been much smaller by having operator* do the search and return the match object as a prvalue. But the old system wouldn't allow that.

Given such an iterator, auto& cannot be used to capture "references" to their results.

like image 68
Nicol Bolas Avatar answered Nov 09 '22 14:11

Nicol Bolas