How to implement operator-> for an iterator that builds its values ​​on demand?

I have a C ++ class that acts like a container: it has member functions size() and operator[] . The values ​​stored in the container are std::tuple objects. However, the container does not actually store tuples in memory; instead, it creates them on demand based on basic data stored in a different form.

 std::tuple<int, int, int> MyContainer::operator[](std::size_t n) const { // Example: draw corresponding elements from parallel arrays return { underlying_data_a[n], underlying_data_b[n], underlying_data_c[n] }; } 

Therefore, the return type operator[] is a temporary object, not a reference. (This means that this is not an lvalue value, so the container is read-only, this is normal.)

Now I am writing an iterator class that can be used to move tuples in this container. I would like to model a RandomAccessIterator , which depends on the InputIterator , but the InputIterator requires support for the expression i->m (where i is an iterator instance), and as far as I can tell, the operator-> function is required to return the pointer.

Naturally, I cannot return a pointer to a temporary tuple constructed on demand. One possibility that comes to mind is to put an instance of the tuple in the iterator as a member variable and use it to store a copy of any value that the iterator currently occupies:

 class Iterator { private: MyContainer *container; std::size_t current_index; // Copy of (*container)[current_index] std::tuple<int, int, int> current_value; // ... }; 

However, updating the stored value requires the iterator to check whether its current index is smaller than the size of the container, so that the past end iterator does not cause undefined behavior by accessing the end of the underlying arrays. This adds (a small amount) of overhead at runtime - not enough to make a decision inappropriate, of course, but it seems a little inelegant. An iterator does not need to store anything but a pointer to its iteration of the container and the current position inside it.

Is there a clean, well-established way to support operator-> for types of iterators that build their values ​​on demand? How could other developers do this?

(Note that I don’t need to support operator-> - I implement the iterator basically so that the container can be moved using C ++ 11 β€œ range for ”, and std::tuple does not have any members that usually would like to get through -> anyway. But I would like to model the iterator concepts correctly, it seems to me that I cut corners differently. Or am I just not tired?)

+6
source share
2 answers
 template<class T> struct pseudo_ptr { T t; T operator*()&&{return t;} T* operator->(){ return &t; } }; 

then

 struct bar { int x,y; }; struct bar_iterator:std::iterator< blah, blah >{ // ... pseudo_ptr<bar> operator->() const { return {**this}; } // ... }; 

It depends on how it works -> .

ptr->b for the ptr simple (*ptr).b .

Otherwise, it is defined as (ptr.operator->())->b . This is recursively evaluated if operator-> does not return a pointer.

pseudo_ptr<T> above gives you a wrapper around a copy of T

Please note, however, that life extension does not work. The result is fragile.

+2
source

Here's an example based on the fact that operator-> is reapplied until a pointer is returned. We do Iterator::operator-> return the Contained object as temporary. This forces the compiler to reuse operator-> . Then we do Contained::operator-> just return a pointer to ourselves. Note that if we do not want to put operator-> in the Contained on-fly object, we can wrap it in a helper object that returns a pointer to the internal Content object.

 #include <cstddef> #include <iostream> class Contained { public: Contained(int a_, int b_) : a(a_), b(b_) {} const Contained *operator->() { return this; } const int a, b; }; class MyContainer { public: class Iterator { friend class MyContainer; public: friend bool operator!=(const Iterator &it1, const Iterator &it2) { return it1.current_index != it2.current_index; } private: Iterator(const MyContainer *c, std::size_t ind) : container(c), current_index(ind) {} public: Iterator &operator++() { ++current_index; return *this; } // -> is reapplied, since this returns a non-pointer. Contained operator->() { return Contained(container->underlying_data_a[current_index], container->underlying_data_b[current_index]); } Contained operator*() { return Contained(container->underlying_data_a[current_index], container->underlying_data_b[current_index]); } private: const MyContainer *const container; std::size_t current_index; }; public: MyContainer() { for (int i = 0; i < 10; i++) { underlying_data_a[i] = underlying_data_b[i] = i; } } Iterator begin() const { return Iterator(this, 0); } Iterator end() const { return Iterator(this, 10); } private: int underlying_data_a[10]; int underlying_data_b[10]; }; int main() { MyContainer c; for (const auto &e : c) { std::cout << ea << ", " << eb << std::endl; } } 
+1
source

Source: https://habr.com/ru/post/979981/


All Articles