Is it legal to compare iterators from different containers?
std::vector<int> foo;
std::vector<int> bar;
Does the expression foo.begin() == bar.begin() yield false or undefined behavior?
(I am writing a custom iterator and stumbled upon this question while implementing operator==.)
If you consider the C++11 standard (n3337):
§ 24.2.1 — [iterator.requirements.general#6]
An iterator j is called reachable from an iterator i if and only if there is a finite sequence of applications of the expression ++i that makes i == j. If j is reachable from i, they refer to elements of the same sequence.
§ 24.2.5 — [forward.iterators#2]
The domain of == for forward iterators is that of iterators over the same underlying sequence.
Given that RandomAccessIterator must satisfy all requirements imposed by ForwardIterator, comparing iterators from different containers is undefined.
The LWG issue #446 talks specifically about this question, and the proposal was to add the following text to the standard (thanks to #Lightness Races in Orbit for bringing it to attention):
The result of directly or indirectly evaluating any comparison function or the binary - operator with two iterator values as arguments that were obtained from two different ranges r1 and r2 (including their past-the-end values) which are not subranges of one common range is undefined, unless explicitly described otherwise.
Undefined behavior as far as I know. In VS 2010 with
/*
* to disable iterator checking that complains that the iterators are incompatible (come from * different containers :-)
*/
#define _HAS_ITERATOR_DEBUGGING 0
std::vector<int> vec1, vec2;
std::vector<int>::iterator it1 = vec1.begin();
std::vector<int>::iterator it2 = vec2.begin();
if (it1 == it2)
{
std::cout << "they are equal!!!";
}
The equality test returns in this case true :-), since the containers are empty and the _Ptr member of the iterators are both nullptr.
Who knows maybe your implementation does things differently and the test would return false :-).
EDIT:
See C++ Standard library Active Issues list "446. Iterator equality between different containers". Maybe someone can check the standard to see if the change was adopted?
Probably not since it is on the active issues list so Charles Bailey who also answered this is right it's unspecified behavior.
So I guess the behavior could differ (at least theoretically) between different implementations and this is only one problem.
The fact that with iterator debugging enabled in the STL implementation that comes with VS checks are in place for this exact case (iterators coming from different containers) singnals at least to me once more that doing such comparisons should be avoided whenever possible.
You cannot directly compare iterators from different containers. An iterator is an object that uses the internal state of a container to traverse it; comparing the internals of one container to another simply does not make sense.
However, if the iterators resulting from container.begin() are available, it may make sense to compare iterators by the count of objects traversed from begin() to the current iterator value. This is done using std::distance:
int a = std::distance(containerA.begin(), iteratorA);
int b = std::distance(containerB.begin(), iteratorB);
if (a <comparison> b)
{ /* ... */ }
Without more context, it's difficult to judge whether this would solve your problem or not. YMMV.
No. If it were legal, this would imply that pointers would not be iterators.
I believe that it is unspecified behaviour (C++03). std::vector iterators are random access iterators and the behaviour of == is defined in the requirements for forward iterators.
== is an equivalence relation
Note that this is a requirement on a type, so must be applicable (in this case) to any pair of valid (dereferencable or otherwise) std::vector::iterators. I believe that this means == must give you a true/false answer and can't cause UB.
— If a and b are equal, then either a and b are both dereferenceable or else neither is dereferenceable.
Conversely, a dereferenceable iterator cannot compare equal to an iterator that is not dereferenceable.
— If a and b are both dereferenceable, then a == b if and only if *a and *b are the same object.
Note the lack of requirement on whether a == b for two iterators that aren't dereferenceable. So long as == is transitive (if a.end() == b.end() and b.end() == c.end() then a.end() == c.end()), reflexive (a.end() == a.end()) and symmetric (if a.end() == b.end() then b.end() == a.end()) it doesn't matter if some, all or no end() iterators to different containers compare equal.
Note, also, that this is in contrast to <. < is defined in terms of b - a, where a and b are both random access iterators. A pre-condition of performing b - a is that there must be a Distance value n such that a + n == b which requires a and b to be iterators into the same range.
ISO/IEC 14882:2003(E) 5.10.1
The == (equal to) and the != (not equal to) operators have the same semantic restrictions, conversions, and result type as the relational operators except for their lower precedence and truth-value result. [ .. ] Pointers to objects or functions of the same type (after pointer conversions) can be compared for equality. Two pointers of the same type compare equal if and only if they are both null, both point to the same function, or both represent the same address (3.9.2).
Simulation Results on XCode (3.2.3):
#include <iostream>
#include <vector>
int main()
{
std::vector <int> a,aa;
std::vector <float> b;
if( a.begin() == aa.begin() )
std::cout << "\n a.begin() == aa.begin() \n" ;
a.push_back(10) ;
if( a.begin() != aa.begin() )
std::cout << "\n After push back a.begin() != aa.begin() \n" ;
// Error if( a.begin() == b.begin() )
return 0;
}
Output :
a.begin() == aa.begin()
After push back a.begin() != aa.begin()
I don't get the requirements on input iterators from the standard 100%, but from there on (forward/bidirectional/random access iterators) there are no requirements on the domain of ==, so it must return false result in an equivalence relation. You can't do < or > or subtraction on iterators from different containers though.
Edit: It does not have to return false, it has to result in an equivalence relation, this allows .begin() of two empty containers to compare equal (as shown in another answer). If the iterators are dereferencable, a == b => *a == *b has to hold. It's still not undefined behaviour.
Related
I'm writing a special iterator class that works like std::istream_iterator and many others by using a default-constructed instance to mark end of iteration. I want to give it the bidirectional iterator category. After running the following code:
MyIterType i_cur(get_some_iter()), i_end;
while(i_cur != i_end) ++i_cur;
do the standard requirements for bidirectional iterators impose the following to be valid?
--i_cur
++i_cur
--i_end or ++i_end
Thank you for quoting the standard if possible. I compile in C++03, but if C++11 introduces changes, I'm also interested to know them.
The Standard says that the answer to your question is yes:
[C++03] : 24.1.4 Bidirectional iterators
Table 75—Bidirectional iterator requirements (in addition to forward iterator)
expression return type operational assertion/note
semantics pre/post-coindition
======== ============ ============ ====================
--r X& pre: there exists s such
that r == ++s.
post: s is dereferenceable.
--(++r) == r.
--r == --s implies r
== s.
&r == &--r
In 24.2.6 in C++11, Table 110 gives the additional requirements for a bidirectional iterator.
In particular, if there exists s such that r == ++s, then --r must be valid and the resulting r must be dereferenceable. In addition:
--(++r) == r.
--r == --s implies r == s.
&r == &--r.
So unless the initial i_cur returned by get_some_iter is a past-the-end iterator, the final i_cur has a predecessor and so must be decrementable. The same holds for i_end, since it is the successor of the predecessor of the final i_cur.
I'm working with a std::map<std::string, MyClass* >.
I want to test if my_map.find(key) returned a specific pointer.
Right now I'm doing;
auto iter = my_map.find(key);
if ((iter != my_map.end()) && (iter->second == expected)) {
// Something wonderful has happened
}
However, the operator * of the iterator is required to return a reference. Intuitively I'm assuming it to be valid and fully initialized? If so, my_map.end()->second would be NULL, and (since NULL is never expected), I could reduce my if statement to:
if (iter->second == expected)
Is this valid according to specification? Does anyone have practical experience with the implementations of this? IMHO, the code becomes clearer, and possibly a tiny performance improvement could be achieved.
Intuitively I'm assuming it to be valid and fully initialized?
You cannot assume an iterator to an element past-the-end of a container to be dereferenceable. Per paragraph 24.2.1/5 of the C++11 Standard:
Just as a regular pointer to an array guarantees that there is a pointer value pointing past the last element
of the array, so for any iterator type there is an iterator value that points past the last element of a
corresponding sequence. These values are called past-the-end values. Values of an iterator i for which the
expression *i is defined are called dereferenceable. The library never assumes that past-the-end values are
dereferenceable. [...]
However, the operator *of the iterator is required to return a reference. Intuitively I'm assuming it to be valid and fully initialized?
Your assumption is wrong, dereferencing iterator that points outside of container will lead to UB.
24.2 Iterator requirements [iterator.requirements]
24.2.1 In general [iterator.requirements.general]
7 Most of the library’s algorithmic templates that operate on data structures have interfaces that use ranges.
A range is a pair of iterators that designate the beginning and end of the computation. A range [i,i) is an
empty range; in general, a range [i,j) refers to the elements in the data structure starting with the element
pointed to by i and up to but not including the element pointed to by j. Range [i,j) is valid if and only if
j is reachable from i. The result of the application of functions in the library to invalid ranges is undefined.
Even without checking the specs, you can easily see that dereferencing an iterator at end has to be invalid.
A perfectly natural implementation (the de-factor standard implementation for vector<>) is for end() to be literally a memory pointer that has a value of ptr_last_element + 1, that is, the pointer value that would point to the next element - if there was a next element.
You cannot possibly be allowed to dereference the end iterator because it could be a pointer that would end up pointing to either the next object in the heap, or perhaps an overflow guard area (so you would dereference random memory), or past the end of the heap, and possibly outside of the memory space of the process, in which case you might get an Access Violation exception when dereferencing).
If iter == my_map.end (), then dereferencing it is undefined behavior; but you're not doing that here.
auto iter = my_map.find(key);
if ((iter != my_map.end()) && (iter->second == expected)) {
// Something wonderful has happened
}
If iter != my_map.end() is false, then the second half of the expression (iter->second == expected) will not be exectuted.
Read up on "short-circut evaluation".
Analogous valid code for pointers:
if ( p != NULL && *p == 4 ) {}
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.
This question already has answers here:
What happens if you increment an iterator that is equal to the end iterator of an STL container
(8 answers)
Closed 5 years ago.
What is the behavior of std::advance when you have say:
std::vector<int> foo(10,10);
auto i = foo.begin();
std::advance(i, 20);
What is the value of i? Is it foo.end()?
The standard defines std::advance() in terms of the types of iterator it's being used on (24.3.4 "Iterator operations"):
These function templates use + and - for random access iterators (and are, therefore, constant time for them); for input, forward and bidirectional iterators they use ++ to provide linear time implementations.
The requirements for these operations on various iterator types are also outlined in the standard (in Tables 72, 74, 75 and 76):
For an input or forward iterator
++r precondition: r is dereferenceable
for a bidirectional iterator:
--r precondition: there exists s such that r == ++s
For random access iterators, the +, +=, -, and -= operations are defined in terms of the bidirectional & forward iterator prefix ++ and -- operations, so the same preconditions hold.
So advancing an iterator beyond the 'past-the-end' value (as might be returned by the end() function on containers) or advancing before the first dereferenceable element of an iterator's valid range (as might be returned by begin() on a container) is undefined behavior since you're violating the preconditions of the ++ or -- operation.
Since it's undefined behavior you can't 'expect' anything in particular. But you'll likely crash at some point (hopefully sooner rather than later, so you can fix the bug).
According to the C++ Standard §24.3.4 std::advance(i, 20) has the same effect as for ( int n=0; n < 20; ++n ) ++i; for positive n. From the other side (§24.1.3) if i is past-the-end, then ++i operation is undefined. So the result of std::advance(i, 20) is undefined.
You are passing the foo size by advancing to 20th position. Definitely it is not end of the vector. It should invoke undefined behavior on dereferencing, AFAIK.
Edit 1:
#include <algorithm>
#include <vector>
#include <iostream>
int main()
{
std::vector<int> foo(10,10) ;
std::vector<int>::iterator iter = foo.begin() ;
std::advance(iter,20);
std::cout << *iter << "\n" ;
return 0;
}
Output: 0
If it is the vector's last element, then it should have given 10 on iterator dereferencing. So, it is UB.
IdeOne Results
That is probably undefined behavior. The only thing the standard says is:
Since only random access iterators provide + and - operators, the library provides two function templates advance and distance. These function templates use + and - for random access iterators (and are, therefore, constant time for them); for input, forward and bidirectional iterators they use ++ to provide linear time implementations.
template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n);
Requires: n shall be negative only for bidirectional and random access iterators. Effects: Increments (or decrements for negative n) iterator reference i by n.
From the SGI page for std::advance:
Every iterator between i and i+n
(inclusive) is nonsingular.
Therefore i is not foo.end() and dereferencing will result in undefined behavior.
Notes:
See this question for more details about what (non)singular means when referring to iterators.
I know that the SGI page is not the de-facto standard but pretty much all STL implementations follow those guidelines.
Does the C++ Standard say I should be able to compare two default-constructed STL iterators for equality? Are default-constructed iterators equality-comparable?
I want the following, using std::list for example:
void foo(const std::list<int>::iterator iter) {
if (iter == std::list<int>::iterator()) {
// Something
}
}
std::list<int>::iterator i;
foo(i);
What I want here is something like a NULL value for iterators, but I'm not sure if it's legal. In the STL implementation included with Visual Studio 2008, they include assertions in std::list's operator==() that preclude this usage. (They check that each iterator is "owned" by the same container and default-constructed iterators have no container.) This would hint that it's not legal, or perhaps that they're being over-zealous.
OK, I'll take a stab. The C++ Standard, Section 24.1/5:
Iterators can also have singular
values that are not associated with
any container. [Example: After the
declaration of an uninitialized
pointer x (as with int* x;), x must
always be assumed to have a singular
value of a pointer. ] Results of most
expressions are undefined for singular
values; the only excep- tion is an
assignment of a non-singular value to
an iterator that holds a singular
value.
So, no, they can't be compared.
This is going to change in C++14. [forward.iterators] 24.2.5p2 of N3936 says
However, value-initialized iterators may be compared and shall compare
equal to other value-initialized iterators of the same type.
I believe you should pass a range to the function.
void fun(std::list<int>::iterator beg, std::list<int>::iterator end)
{
while(beg != end)
{
// do what you want here.
beg++;
}
}
Specification says that the postcondition of default constructor is that iterator is singular. The comparison for equality are undefined, so it may be different in some implementation.