Rawler
Rawler

Reputation: 1610

Is ->second defined for iterator std::map::end()?

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.

Upvotes: 13

Views: 5535

Answers (4)

Marshall Clow
Marshall Clow

Reputation: 16670

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 ) {}

Upvotes: 2

Euro Micelli
Euro Micelli

Reputation: 33998

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

Upvotes: 2

Andy Prowl
Andy Prowl

Reputation: 126432

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. [...]

Upvotes: 14

alexrider
alexrider

Reputation: 4463

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.

Upvotes: 3

Related Questions