Overloading operator < for non-random iterators - c++

I wanted to know if we could overload the operator < for non-random iterators like those of std::list, std::map, etc. Say for example if I overload it for std::list then :
bool operator < (std::list<T>::iterator &i1, std::list<T>::iterator &i2)
{
return (&*i1 < &*i2);
}
My main purpose is to do an iteration like this :
for (auto i = l.begin(); i < l.end(); ++i) // possible for std::vector, std::deque, etc
// I want to do this instead of i != l.end()
But the compiler says :
[Error] declaration of operator< as non-function
Anyone has any solutions ?

It just doesn't make sense to do a less than comparision on iterators that do not provide it. Using your example with a std::list the nodes of the list could be anywhere in memory. Trying to compare the addresses of the nodes is pointless as the first node could have a higher address than all of the other nodes. If that is the case then you would never loop through the list. The only way this works is to have a sentinel node(end) and every iteration check to see if you are not equal to it. By doing this you know you have not reached the end and you can continue. Once you compare equal to the end then you know you have reached end of the list.

You are working in the wrong direction. Non-random-access iterator does not support operation < for a reason. Basically, it is not possible to implement operation < in a rational way for non-random-access iterators. In your case, you should write:
for (auto i = l.begin(); i != l.end(); ++i)
instead. Or, if C++11 is supported, consider the possibility of using a range-based for loop.
As to your code:
bool operator < (std::list<T>::iterator &i1, std::list<T>::iterator &i2)
{
return (&*i1 < &*i2);
}
It is not OK in two ways.
std::list<T>::iterator is a dependent name. You need to qualify it with typename.
Template argument for T cannot be deduced from the iterator type. It must be specified explicitly.
So, how to solve the second issue? Well, I don't think it is possible. The actual type of the iterator is not specified in the standard, and is considered an implementation detail.

I'd suggest using std::remove_if (and then erase of course), this is much clearer what you are doing and should also be efficient. If you can use C++11 then the predicate can be a lambda and it's really compact.

Related

How to define a C++ iterator that skips tombstones

I am implementing a container that presents a map-like interface. The physicals implementation is an std::vector<std::pair<K*, T>>. A K object remembers its assigned position in the vector. It is possible for a K object to get destroyed. In that case its remembered index is used to zero out its corresponding key pointer within the vector, creating a tombstone.
I would like to expose the full traditional collection of iterators, though I think that they need only claim to be forward_iterators (see next).
I want to be able to use range-based for loop iteration to return the only non-tombstoned elements. Further, I would like the implementation of my iterators to be a single pointer (i.e. no back pointer to the container).
Since the range-based for loop is pretested I think that I can implement tombstone skipping within the inequality predicate.
bool operator != (MyInterator& cursor, MyIterator stop) {
while (cursor != stop) {
if (cursor->first)
return true;
++cursor;
}
return false;
}
Is this a reasonable approach? If yes, is there a simple way for me to override the inequality operator of std::vector's iterators instead of implementing my iterators from scratch?
If this is not a reasonable approach, what would be better?
Is this a reasonable approach?
No. (Keep in mind that operator!= can be used outside a range-based for loop.)
Your operator does not accept a const object as its first parameter (meaning a const vector::iterator).
You have undefined behavior if the first parameter comes after the second (e.g. if someone tests end != cur instead of cur != end).
You get this weird case where, given iterators a and b, it might be that *a is different than *b, but if you check if (a != b) then you find that the iterators are equal and then *a is the same as *b. This probably wrecks havoc with the multipass guarantee of forward iterators (but the situation is bizarre enough that I would want to check the standard's precise wording before passing judgement). Messing with people's expectations is inadvisable.
There is no simple way to override the inequality operator of std::vector's iterators.
If this is not a reasonable approach, what would be better?
You already know what would be better. You're just shying away from it.
Implement your own iterators from scratch. Wrapping your vector in your own class has the benefit that only the code for that class has to be aware that tombstones exist.
Caveat: Document that the conditions that create a tombstone also invalidate iterators to that element. (Invalid iterators are excluded from most iterator requirements, such as the multipass guarantee.)
OR
While your implementation makes a poor operator!=, it could be a fine update or check function. There's this little-known secret that C++ has more looping structures than just range-based for loops. You could make use of one of these, for example:
for ( cur = vec.begin(); skip_tombstones(cur, vec.end()); ++cur ) {
auto& element = *cur;
where skip_tombstones() is basically your operator!= renamed. If not much code needs to iterate over the vector, this might be a reasonable option, even in the long term.

Vector iterators < or !=

Could anyone help me understand whether there's a big difference in != and < when it comes to talk about vector iterators within a for loop?
I mean, no matter whether you use != and <, the result should be the same?
for (vector<int>::iterator i = vec.begin(); i != vec.end(); i++)
// DO STUFF
for (vector<int>::iterator i = vec.begin(); i < vec.end(); i++)
// DO STUFF
I am aware that the most common way is to use !=, but would < be a big issue if used?
operator< is only supported for random access iterators. std::vector::iterator is a random access iterator, so both i != vec.end() and i < vec.end() are supported and valid and make no difference in your example.
If you had a container that does not support random access iterators (e.g. std::list), i < list.end() would not compile.
The general recommendation is to use postfix increment only when it is necessary though because it may create an unnecessary copy when the iterator is non-trivial, so ++i is cleaner and may be faster.
Also, if the loop calls a function whose definition is not available in this translation unit vec.end() is going to be reloaded from memory on each loop iteration, which might cause an unnecessary cache miss. You can avoid that reload by saving the value into a local variable, so that the compiler is certain that the local variable is inaccessible to any other function:
for(vector<int>::iterator i = vec.begin(), j = vec.end(); i < j; ++i)
// ...
Even better, you may like to use range-for loops that avoid these performance pitfalls for you:
for(auto const& elem : vec)
// ...
The entire philosophy behind the STL part of the Standard Library (the containers, iterators and algorithms) is to minimize the programatic distinctions between the containers. They exhibit different properties but how you program them is designed to be as similar as possible.
This makes them easier to learn and easier to use generically. That means you can write one generic function (or algorithm) and have it apply to any other container (or as many as possible).
With that in mind it is beneficial to use syntax that is common to all containers and iterators where possible.
Only some containers' iterators allow < comparisons but all containers' iterators accept !=. For that reason I would recommend always using != as a matter of consistency and to facilitate your code being easily ported to a different container.
It does make a difference, although not for std::vector. All iterators are equality comparable, so != will always work. Only random access iterators are less than comparable, as is std::vector, so in your case it wouldn't be a big issue.

Why doesn't the standard allow std::for_each to have well-defined behavior on invalid random access iterator ranges?

In this question it was explained that std::for_each has undefined behavior when given an invalid iterator range [first, last) (i.e. when last is not reachable by incrementing first).
Presumably this is because a general loop for(auto it = first; it != last; ++it) would run forever on invalid ranges. But for random access iterators this seems an unnecessary restriction because random access iterators have a comparison operator and one could write explicit loops as for(auto it = first; it < last; ++it). This would turn a loop over an invalid range into a no-op.
So my question is: why doesn't the standard allow std::for_each to have well-defined behavior on invalid random access iterator ranges? It would simplify several algorithms which only make sense on multi-element containers (sorting e.g.). Is there a performance penalty for using operator<() instead of operator!=() ?
This would turn a loop over an invalid range into a no-op.
That's not necessarily the case.
One example of an invalid range is when first and last refer to different containers. Comparing such iterators would result in undefined behaviour in at least some cases.
This would turn a loop over an invalid range into a no-op.
You seem to be saying that operator< should always return false for two random-access iterators that are not part of the same range. That's the only way your specified loop would be a no-op.
It doesn't make sense for the standard to specify this. Remember that pointers are random-access iterators. Think about the implementation burden for pointer operations, and the general confusion caused to readers, if it were defined that the following code print "two":
int a[5];
int b[5]; // neither [a,b) nor [b,a) is a valid range
if ((a < b) || (b < a)) {
std::cout << "one\n";
} else {
std::cout << "two\n";
}
Instead, it is left undefined so that people won't write it in the first place.
Because that's the general policy. All using < would allow is things
like:
std::for_each( v.begin() + 20, v.begin() + 10, op );
Even with <, passing an invalid iterator, or iterators from different
containers, is undefined behavior.

iterator successor

I want to initialize an iterator (of arbitrary kind) with the successor of another iterator (of the same kind). The following code works with random access iterators, but it fails with forward or bidirectional iterators:
Iterator i = j + 1;
A simple workaround is:
Iterator i = j;
++i;
But that does not work as the init-stament of a for loop. I could use a function template like the following:
template <typename Iterator>
Iterator succ(Iterator it)
{
return ++it;
}
and then use it like this:
Iterator i = succ(j);
Is there anything like that in the STL or Boost, or is there an even better solution I am not aware of?
I think you're looking for next in Boost.Utility. It also has prior for obtaining an iterator to a previous element.
Update:
C++11 introduced std::next and std::prev.

C++ -- STL Vector::const_iterator why not use < xx.end()?

// display vector elements using const_iterator
for ( constIterator = integers.begin();
constIterator != integers.end(); ++constIterator )
cout << *constIterator << ' ';
Can we use constIterator < integers.end()?
Thank you
operator< is only defined for random access iterators. These are provided, for example, by std::vector and std::string, containers that, in essence, store their data in contiguous storage, where iterators are usually little more than wrapped pointers. Iterators provided by, e.g., std::list are only bidirectional iterators, which only provide comparison for equality.
Traditionally, it's seen as defensive programming to use < instead of !=. In case of errors (for example, someone changes ++i to i+=2) the loop will terminate even though the exact end value is never reached. However, another view at this is that it might mask an error, while the loop running endlessly or causing a crash would make the error apparent.
Yes, and you can also use operator < for deque::(const_)iterator, but it won't work for iterators for any other containers.
The working of operator < is guaranteed because vector and deque provide a Random Access Iterator.