Why are C++ iterators required to return a reference?
Asked Answered
S

2

21

I'm implementing an iterator that iterates over the results of a generator function rather than over a data structure in memory such as a vector or map.

Reading through the final working draft for C++17 §27.2.3, the return type of the dereference operator for an input iterator (and by extension, most other iterators) a forward iterator is required to be a reference. This is fine for items that exist in the data structure the iterator is iterating over. However, because I'm not using a data structure and am calculating each item when the dereference operator is called, I don't have a valid reference to return; the calculated item is destroyed when the operator returns. To work around this, I am storing the result of the calculation in the iterator itself and returning a reference to the stored result. This works fine for my use case, but has issues of its own when used with arbitrary user-defined types.

I can understand iterators being allowed to return a reference, but why would this be a requirement for non-mutating iterators? Did the writers of the standard not consider generators and on-the-fly transformations to be valid use cases for iterators? Would returning a value instead of a const reference cause any actual harm?

[edit]: I'm asking more out of curiosity about why the standard is written the way it is, since I already have a perfectly good workaround.

Smug answered 8/10, 2017 at 1:52 Comment(5)
I think a good example to workout an answer (from an expert) is std::istream_string. Having said that my gut feeling is that returning a value T is not better than returning a const T&, because there may be situation where dereferencing will like to return an actual temporary and some other times in which it would like to return an actual preexisting element.Blunger
@alfc There is no such thing as istream_string. Do you mean istringstream? Because that doesn't use iterators, and if it did the characters it returns would still be coming from a buffer somewhere.Smug
Ah, I see, it's istream_iterator.Smug
Yes, I meant that.Blunger
This is especially weird given that a local const T& on a prvalue extends the lifetime of the prvalue until the end of the scope of the reference. It seems most algorithms that assume that a Forward (or above) output iterator returns const T& would work just fine with prvalues. But I understand the wording doesn't seem to agree with thatJacquie
C
17

Dereferencing an input iterator is not required to yield a glvalue (that is, return a reference). The input iterator requirements say that the return type when dereferencing must be "reference, convertible to T" but nowhere does it say that reference must be a reference type.

However, dereferencing a forward iterator is required to yield a glvalue:

if X is a mutable iterator, reference is a reference to T; if X is a constant iterator, reference is a reference to const T,

So go ahead and write your iterator that generates elements on the fly, but it can only be an input iterator, not a forward iterator. For many algorithms this is sufficient (e.g., std::for_each and std::all_of).

Clop answered 8/10, 2017 at 2:6 Comment(13)
It is good to know that when the standard says reference they don't mean "reference". If so, many things are starting to make more sense to me.Blunger
@Blunger if this answer helped you, please consider accepting itClop
Hey, I didn't ask the question. :)Blunger
@Blunger — look more carefully at the specifications for iterators. Each iterator type defines its own reference type, and that’s what the rest of the iterator specification uses.Reefer
@PeteBecker Sure, and it is not the first time I see or set myself reference to something that is not a reference, and more importantly something that doesn't have reference semantics. I always did this thinking that the standard was too constraining. Brian's answer makes a lot of sense to me in perspective.Blunger
Okay, so X::reference is not required to be an actual reference type for input iterators (which is somewhat weird and unexpected, given the name). This still doesn't answer why, according to the standard, the more specialized iterators are required to return a reference that is an actual reference.Smug
I don't know the "legal" answer. But it seem that a mutable iterator it must be such that auto& a = *it; a = b; would modify something "pointed" by it. I don't know if that strictly requires *it to return a reference (T&), but surely it is something that resembles a reference, it could be a fancy or a generalization reference I suppose. But since generalizations of references are still hard in C++ (due to lack of dot-overloading) that means that it is probable a common reference #34236318Blunger
@Blunger sorry to revive an old thread, but im still not sure I understand why immutable forward iterators require that they return true const references. In this context, the semantics of returning a value and const reference seem like they are the same? My intuition is that the standard requires true reference simply because the iterator tags are not continuous enough to correctly represent all iterator types. (i.e. immutable forward iterator.)Bartell
@mgoldman, I don't understand your comment. Do you think is an oversight? Can you give an example?Blunger
@Blunger My thought is that iterators should have a much more nuanced type hierarchy, but in order to simplify that hierarchy some restrictions are enforced that are only necessary for what should be a more refined iterator type. My other point is that requiring immutable forward iterators to return const reference seems pointless. The semantics of value and const & are the same from the iterator perspective. (fourth bullet under requirements en.cppreference.com/w/cpp/concept/ForwardIterator)Bartell
@mgoldman, wouldn't that requirement avoid a copy if not necessary? mytype const& v = *it would avoid a copy if (in generic code) the *it coincides with mytype and if not, then it will make a "persistent" copy hold by v).Blunger
that is true but I wouldnt think that performance would define a concept. all of those algorithms will compile just fine.Bartell
@alfC, I agree with mgoldman. I am just writing an algorithm iterator (or is it an iterator algorithm?), where a "container" is given two sets of begin and end iterators (IT1 and IT2) and computes the result through an iterator. The return type when dereferencing the iterator is std::pair<T1*, T2*> where .first or .second may point to an element pointed at by one of the iterators while they walk to their end iterator or may be a nullptr. If a forward iterator must be const T&, does this invalidate my iterator being an iterator? In what context will this not perform correctly?Aggappera
W
2

It appears that as of c++20 that requirement has been relaxed:

Notes:

Unlike the LegacyForwardIterator requirements, the forward_iterator concept does not require dereference to return a reference.

https://en.cppreference.com/w/cpp/iterator/forward_iterator https://isocpp.org/files/papers/N4860.pdf#page=920

Wizen answered 16/10, 2023 at 20:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.