Reference invalidation after applying reverse_iterator on a custom made iterator
c++, c++11, c++14, reverse-iterator
Solution
`reverse_iterator` does not work with so-called "stashing iterators", iterators that returns references to things within themselves. The `operator*` of `reverse_iterator` makes a copy of the wrapped iterator, decrements it, and returns the result of dereferencing the copy. Hence, if dereferencing iterator returns a reference to something inside itself, the reference will become dangling.
An attempt was made in the C++11 specification to make it work, but it turns out that it's impossible to implement without adding massive overhead* for non-stashing iterators, so the specification was reverted to the C++03 version.
* To support "stashing iterators", an additional data member must be added that stores the decremented current iterator, doubling the size of `reverse_iterator`; and then some form of synchronization must be used since `operator *` is `const` - and so must be simultaneously callable from multiple threads without causing a data race - but must modify this additional data member. That's a lot of overhead to add to all `reverse_iterator`s for such an uncommon use case.
Problem
I implemented a bidirectional iterator, however instead of operating on a data structure, it returns a mathematical series which one can iteratively calculate through in both directions. In fact, I'm iterating through the integers, using ++ and -- on an int. This means that the data is not stored in a different structure, and hence when the iterator goes out of scope, so does the value. Nevertheless, I would expect the next code (minimal failing example) sample to work, as the iterator stays in scope the whole time. But it does not work :( ``` #include <iostream> #include <iterator> #include <vector> class my_iterator : public std::iterator<std::bidirectional_iterator_tag, int> { int d_val = 12; public: my_iterator operator--(int) { std::cout << "decrement--\n"; return my_iterator(); } my_iterator &operator--() { std::cout << "--decrement\n"; return *this; } my_iterator operator++(int) { std::cout << "increment++\n"; return my_iterator(); } my_iterator &operator++() { std::cout << "++increment\n"; return *this; } int &operator*() { std::cout << "*dereference\n"; return d_val; } bool operator==(my_iterator const &o) { return false; } bool operator!=(my_iterator const &o) { return true ; } }; int main() { auto it = std::reverse_iterator<my_iterator>(); int &i = *it; if (true) { std::cout << i << '\n'; } else { std::vector<int> vec; vec.push_back(i); std::cout << vec[0] << '\n'; } } ``` source: http://ideone.com/YJKvpl The if-branch results in memory violations, as properly detected by valgrind: ``` --decrement *dereference ==7914== Use of uninitialised value of size 8 ==7914== at 0x4EC15C3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20) ==7914== by 0x4EC16FB: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20) ==7914== by 0x4EC1C7C: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20) ==7914== by 0x4ECEFB9: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20) ==7914== by 0x40087B: main (divine.cc:25) ==7914== ==7914== Conditional jump or move depends on uninitialised value(s) ==7914== at 0x4EC15CF: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20) ==7914== by 0x4EC16FB: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20) ==7914== by 0x4EC1C7C: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20) ==7914== by 0x4ECEFB9: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20) ==7914== by 0x40087B: main (divine.cc:25) ==7914== ==7914== Conditional jump or move depends on uninitialised value(s) ==7914== at 0x4EC1724: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20) ==7914== by 0x4EC1C7C: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20) ==7914== by 0x4ECEFB9: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20) ==7914== by 0x40087B: main (divine.cc:25) ==7914== 12 ==7914== ==7914== HEAP SUMMARY: ==7914== in use at exit: 0 bytes in 0 blocks ==7914== total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==7914== ==7914== All heap blocks were freed -- no leaks are possible ==7914== ==7914== For counts of detected and suppressed errors, rerun with: -v ==7914== Use --track-origins=yes to see where uninitialised values come from ==7914== ERROR SUMMARY: 5 errors from 3 contexts (suppressed: 0 from 0) ``` The else-branch doesn't result in memory violations, or at least as far as my valgrind can detect. However, the value stored in the vector is 'random': ``` --decrement *dereference -16777520 ``` I'm a bit surprised by what happens. The iterator should be in scope all the time, yet the reference seems to get invalidated. Why do I get the memory violations, whilst 12 is printed or why don't I get them whilst something different from 12 is stored?