41

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==.)

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • Related question: http://stackoverflow.com/questions/844768/how-is-stl-iterator-equality-established – jweyrich Jan 12 '11 at 01:25

7 Answers7

39

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.

Holt
  • 36,600
  • 7
  • 92
  • 139
jweyrich
  • 31,198
  • 5
  • 66
  • 97
4

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.

ds27680
  • 1,993
  • 10
  • 11
3

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.

Blair Holloway
  • 15,969
  • 2
  • 29
  • 28
2

No. If it were legal, this would imply that pointers would not be iterators.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • So comparing arbitrary pointers is illegal? I thought that would only apply to subtracting pointers. – fredoverflow Jan 11 '11 at 12:22
  • I don't get this answer - may be I've not had enough coffee(!) It's the same *type* so I should imagine that this always returns false, but shouldn't invoke UB(?) – Nim Jan 11 '11 at 12:37
  • Comparing two pointers is legal iff (A) they both point to the same object/array or (B) at least one of them is NULL. But even `int a,b; a==b;` is illegal. – MSalters Jan 11 '11 at 12:40
  • 1
    @MSalters: You mean `int a, b; &a == &b;`, right? (Although your example on plaint ints is also illegal, but for a different reason.) – fredoverflow Jan 11 '11 at 12:46
  • 3
    @MSalters: I don’t believe that. Otherwise C++ wouldn’t allow any way to have reference equality, which is kind of important in object oriented code, and most `operator =` implementations would be broken (test for self-assignment). It’s true that it’s illegal to have pointers that point outside an array’s range (or behind it) but that’s different. – Konrad Rudolph Jan 11 '11 at 12:51
  • @FredOverflow: Are you sure that's an error? As I've understood it, pointers to different objects have no well-defined order, but can be compared for equality (comments not really that good a place to discuss this, though). – eq- Jan 11 '11 at 13:13
  • @eq-: What comparison are you referring to, `&a == &b` or `a == b`? – fredoverflow Jan 11 '11 at 13:15
  • @MSalters: I had already heard that comparing pointers from two different arrays was illegal (something to accommodate memory models with far/near distinction if I recall correctly), but I cannot find anything in the standard, would you recall were this stems from ? – Matthieu M. Jan 11 '11 at 14:31
  • 2
    @MSalters: as @jweyrich pointed out, from ForwardIterator and onward, it only makes sense to compare iterators if they belong to the same sequence. There is not even a provision for uninitialized iterators. – Matthieu M. Jan 11 '11 at 14:36
  • @MSalters: I found this discussion regarding operations supported by pointers that seem to correlate your point. Answer by Johannes --> http://stackoverflow.com/questions/1418068/what-are-the-operations-supported-by-raw-pointer-and-function-pointer-in-c-c/1418152#1418152 – Matthieu M. Jan 11 '11 at 14:48
  • @FredOverflow: `&a == &b`. (I'm picking on the 'also illegal' part of your earlier comment). – eq- Jan 12 '11 at 22:18
  • 1
    @eq-: I don't think `&a == &b` is illegal, see Konrad's comment. `a == b`, however, is, because reading an uninitialized variable yields undefined behavior. – fredoverflow Jan 13 '11 at 09:26
2

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.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • I believe there is a typo in "If a and b are both dereferenceable, then a == b if and only if *a and *b are the same object". I would say that if `a == b` THEN `*a == *b`, but the reverse does not hold in the general case. – Matthieu M. Jan 11 '11 at 14:03
  • 1
    @Matthieu M.: That comes direct from the standard. Note: "are the same object" not `*a == *b`. – CB Bailey Jan 11 '11 at 14:08
0

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()

Mahesh
  • 34,573
  • 20
  • 89
  • 115
  • 5
    Just because it works for a special case (pointers) does not mean it is guaranteed by the general case (iterators). – fredoverflow Jan 11 '11 at 12:49
  • @Konrad Rudolph - Iterators seem like they work on pointer arithematic. So, can't iterators be compared to pointers ? – Mahesh Jan 11 '11 at 13:13
  • 3
    Every pointer is an iterator, but not the other way around. For example, `std::list::iterator` does not support "pointer arithmetic". – fredoverflow Jan 11 '11 at 13:24
  • @FredOverflow - `but need not be the other way around`. Thanks. – Mahesh Jan 11 '11 at 13:26
  • http://stackoverflow.com/questions/2661053/isnt-an-iterator-in-c-a-kind-of-a-pointer I read this article and thought iterator is a c kind pointer. – Mahesh Jan 11 '11 at 13:34
0

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.

etarion
  • 16,935
  • 4
  • 43
  • 66
  • 2
    `The domain of == for forward iterators is that of iterators over the same underlying sequence.` § 24.2.5 (C++0x) – jweyrich Jan 11 '11 at 12:59
  • C++03: I think the reduced requirements on the domain of `==` only applies to input iterators. `==` is required to be an equivalence relation _over its domain_ for input iterators but for forward iterators and above `==` is required to be an equivalence relation... full stop. – CB Bailey Jan 11 '11 at 12:59
  • Yes, i was referring to C++03, I don't know have the 0x draft. – etarion Jan 11 '11 at 13:04
  • 1
    @jweyrich: that would warrant an answer I think :) – Matthieu M. Jan 11 '11 at 14:33
  • @Matthieu M.: Not quite, it's from a not-yet-valid standard. And the current one doesn't have any such requirement, also in the case of the OP it's random access iterators. – etarion Jan 11 '11 at 14:39
  • @etarion: RandomAccessIterator is a refinement of ForwardIterator and as such follows the same constraints unless they are redefined. I agree that C++0x is not a standard yet, but I do prefer to make statements for C++0x nonetheless, as 1. it should not evolve much, 2. it solves issues in C++03 and 3. queries in the future are much likely to need answers using C++0x than C++03 given that it'll become prominent in the years to come. – Matthieu M. Jan 11 '11 at 14:45
  • this `unless they are redefined` is the thing, without the full text you can't judge that. You could quote from c++03 about input iterators, but judging forward iterators from that would be wrong. – etarion Jan 11 '11 at 14:51
  • @Matthieu M.: I followed your advice. Thanks! ;-) – jweyrich Jan 12 '11 at 01:23