5

I've implemented a custom iterator based on the answer here:
https://stackoverflow.com/a/31886483/1973454

However, instead of the Type* _ptr member, my code ultimately retrieves a value stored at some singular location, as if it were using the following function:

// my iterator class gets its values from this function
float globalFloat{ 0 };
float* retrieveFloat (int idx)
{
    globalFloat = (float)idx;
    return &globalFloat;
}

This means that in order for two iterators to be used simultaneously (i.e for searching using upper_bound), I've got to cache the float locally before allowing access:

class Iterator : public std::iterator<std::random_access_iterator_tag, float, int>
{
public:
    Iterator () = default;
    Iterator (int idx) : _index (idx) {}

    Iterator& operator++ () noexcept { ++_index; return *this; }
    Iterator& operator-- () noexcept { --_index; return *this; }

    /// ... rest of iterator declaration

    const float& operator* () const { _data = *retrieveFloat (_index); return _data; }
    const float* operator-> () const { _data = *retrieveFloat (_index); return &this->_data; }
    const float& operator[] (int offset) const { _data = *retrieveFloat (_index + offset); return _data; }

private:
    int _index{ 0 };
    mutable float _data{ 0 };
};

What I'm worried about is the last operator[]. According to cppreference:
https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator

the [] operator must return a reference type. However if I were to write the following code:

int main (int argc, char ** argv)
{
    Iterator it;
    if (it[0] == it[1])
        return 0;
    return 1;
}

Then I return 0 because each [] invocation modifies _data.

If I change how I'm subclassing std::iterator and use float as my "reference" type:

class Iterator : public std::iterator<std::random_access_iterator_tag, float, int, float*, float>
{
public:
    /// ... rest of iterator declaration (constructors / operators)

    const float operator* () const { _data = *retrieveFloat (_index); return _data; }
    const float* operator-> () const { _data = *retrieveFloat (_index); return &this->_data; }
    const float operator[] (int offset) const { _data = *retrieveFloat (_index + offset); return _data; }
};

then things work... but somehow that feels dirty. Is doing this kind of thing legal?

I know that if, instead of float, my data type was something heavier to copy then there would be a performance concern, but for argument's sake let's say that I'm only working with floats or lightweight PODs. Let's also assume I have no need of modifying the data being iterated over.

Thanks for any help you can provide, and sorry if I made this question too long. I can edit if need be.

  • John
mynameisjohnj
  • 421
  • 4
  • 12
  • 4
    This doesn't feel like your iterator satisfies all of the requirements for being a [*RandomAccessIterator*](https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator), maybe [*BidirectionalIterator*](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator) might be more appropriate, which case you don't have to worry about `operator[]` – Remy Lebeau Jan 31 '20 at 20:23
  • 2
    The following also needs to work: `it[1] = 1;`. So the return value of `[]` can't be a copy. – rustyx Jan 31 '20 at 20:28
  • 2
    I expect your code will not work because for example std::sort calls std::swap that will try to swap 2 elements that are pointing to the same float. note: Maybe I misunderstood your example. – NoSenseEtAl Jan 31 '20 at 20:30
  • @RemyLebeau That might work for me... but which requirement don't I satisfy (besides the operator* / operator[], which I'm returning by copy)? I do have allof the functions mentioned in the answer I linked implemented, I've just omitted them from this post. – mynameisjohnj Jan 31 '20 at 20:36
  • @rustyx What if I said that I have no need of modifying the underlying data? In my example there is a global float that could be modified, but in my actual use case the data lives inside a shared library and can't be modified. Still this could be a requirement of random_access_iterator. – mynameisjohnj Jan 31 '20 at 20:38
  • @NoSenseEtAl no that's fair, I incorrectly mentioned std::swap. What I actually need is std::upper_bound (or the ability to search a sorted range). – mynameisjohnj Jan 31 '20 at 20:39
  • 1
    @mynameisjohnj would boost counting + transform iterator be better solution for you? https://www.boost.org/doc/libs/1_72_0/libs/iterator/doc/index.html – NoSenseEtAl Jan 31 '20 at 20:41
  • 1
    @NoSenseEtAl it might... this merits a bit more research into different iterator types. Thanks! – mynameisjohnj Jan 31 '20 at 20:42
  • @mynameisjohnj "*What I actually need is std::upper_bound (or the ability to search a sorted range).*" - then all you really need is a [*ForwardIterator*](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) at a minimum, not a *RandomAccessIterator* or *BidirectionalIterator* – Remy Lebeau Jan 31 '20 at 21:14
  • 1
    @RemyLebeau Although, it may be relevant to mention that asymptotical complexity of such search on forward range is worse than on random access range. Random access can use binary search while non-random access is limited to linear search. – eerorika Jan 31 '20 at 21:21

1 Answers1

5

Does std::iterator::reference have to be a reference?

No. std::iterator::reference is not required to be a reference type. It is merely required to be the same type as returned by operator* and required to be convertible to value_type.

However, in order to be an OuputIterator, *r = o must be well-formed, and as such reference must be either a reference type or a class type with non-lvalue ref qualified assignment operator. So, using float is fine for non-output iterators, but not fine for output iterators.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Thanks for clarifying! As some of the other users have pointed out I've broken some requirements about modifying the underlying data (i.e it[1] = 1 should work). Am I subject to those requirements if I ensure that the iterators are used in a const way (i.e never modifying the underlying data)? Perhaps there's an iterator tag that covers this case... i.e const_random_access_iterator. Anyway, thanks again! – mynameisjohnj Jan 31 '20 at 20:46
  • 1
    @mynameisjohnj There's no need for a tag to specify whether it is output iterator or not. – eerorika Jan 31 '20 at 23:07