21

I'm having trouble with vector iterators. I've read in a few places that checking for null iterators isn't possible, and that the usual way to check iterators is to check it against vector.end() after a search. So for example:

vector< Animal* > animalList;

vector<Animal*>::iterator findInList(const type_info& type)
{
    // Loop through list of Animals, if Dog found, return iterator to it
}

auto it = findInList(typeid(Dog));
// With a pointer I can check if it's null, but with an iterator I have to check against animalList.end();

The problem is that the container could be empty. With an iterator I can't return null to indicate the container's empty or the search failed. I can return vector::end(), but cplusplus.com says:

If the container is empty, vector::end() function returns the same as vector::begin()

and then for vector::begin() it says:

If the container is empty, the returned iterator value shall not be dereferenced.

So if I have an empty container, vector::end() and vector::begin() point to the same place, I don't think I can dereference it, and I'm not even sure it's pointing to allocated memory.

Edit: Thanks to everyone. As you have iterated out, vector::end() or vector::begin() do not dereference the iterator, I can safely check against vector::end().

Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • Return an iterator for an empty vector. When the caller loops over the iterator, the loop will end immediately, and they'll never dereference the iterator. – Barmar Dec 27 '16 at 22:30
  • Are you trying to avoid exposing the container you're using internally, and are thus hesitant to provide a way for the user to check the returned value against the `end` of your container themselves? – jaggedSpire Dec 27 '16 at 22:32
  • 1
    Iterators are not pointers. – simon Dec 27 '16 at 22:33
  • *"So if I have an empty container...I don't think I can dereference it"* The same would be true if you were returning pointers that might be null. – Drew Dormann Dec 27 '16 at 22:34
  • @jaggedSpire, Now necessarily. Well I wanted the function to handle as much as possible. I think from the answers I understand I can get the same behaviour checking against vector::end(), that is, the same as returning a pointer and checking against nullptr. – Zebrafish Dec 27 '16 at 22:46
  • @simon but all pointers *are* iterators. Having said that, you don't check a pointer against *nullptr* to see if you have reached the end of an array, you check it against a pointer on-past-the-end of the array – Caleth Sep 07 '21 at 08:26

4 Answers4

30

You don't need to check if the iterator is null, because it will never be. You need to check if the returned iterator is different from the container's end() position. If it is, you can safely dereference the iterator by *it.

If the container is empty, the returned iterator value shall not be dereferenced. So if I have an empty container, vector::end() and vector::begin() point to the same place, I don't think I can dereference it, and I'm not even sure it's pointing to allocated memory.

No, checking if(myIt != container.end()) is not dereferencing the iterator. Iterator dereferencing is done via *myIt, which means getting the value of the object that the iterator is pointing to. It's always safe to check iterators to other iterators from the same container, it's unsafe to dereference iterators not pointing to containers' elements.

Samufi
  • 2,465
  • 3
  • 19
  • 43
paweldac
  • 1,144
  • 6
  • 11
  • Ah I see what I'm misunderstanding. Checking an iterator against .end() isn't dereferencing it, thank you. – Zebrafish Dec 27 '16 at 22:41
  • @TitoneMaurice if I helped please mark question as anwsered, it'll be visiable in google index for other people and won't show on unanwsered list of questions. Thank you. – paweldac Dec 27 '16 at 22:51
  • _"It's always safe to check iterators to other iterators"_ Well, no, the iterators have to be valid (this includes one-past-the-end), and they have to refer to the same container. – Lightness Races in Orbit Dec 27 '16 at 23:03
  • @LightnessRacesinOrbit updated anwser, with information about same container. No stl algorithm will return 'one-past-the-end' iterator. It'll return end(). User must manually increment such iterator, if dev wants to hurt himself, he'll always find a way :) Thank you for pointing it out. – paweldac Dec 27 '16 at 23:07
  • @paweldac: end() _is_ the one-past-the-end iterator. – Lightness Races in Orbit Dec 27 '16 at 23:10
  • @LightnessRacesinOrbit end() is past-the-end iterator, I understood one-past-the-end interator as end()++; – paweldac Dec 27 '16 at 23:13
  • 1
    `end()` returns the one-past-the-end iterator. It's an iterator to one past the end of the container's valid range. That's the terminology the standard uses, it's the terminology the major reference sites use, and it's the terminology I use! I've never heard of a "past-the-end iterator" before. – Lightness Races in Orbit Dec 27 '16 at 23:14
  • *"You need to check if returned iterator is different from containers end() position. If it is, you can safely dereference"* - please not that this only applies in this very scenario. Generally it's false, because if you delete the element an iterator was pointing to, the iterator won't be on the end() position, yet you can't safely dereference it. This answer should mention it as a warning. – vsz Jul 03 '20 at 12:29
3

No, you can't check against NULL because it is not a pointer. Return and also check against animalList.end(). Only when the iterator is not equal to end() should you dereference it.

Mikel F
  • 3,567
  • 1
  • 21
  • 33
3

Just check like this

auto it = findInList( someInfo );

if ( it == animalList.end() ) std::cout << "not found";
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 9
    This is not good style. You've abstracted away the container behind a function called `findInList`, but now you have to immediately break that abstraction and compare against the container directly. Either use `.find` in the first place, or find another way to signal a not-found condition. – Lightness Races in Orbit Dec 27 '16 at 23:07
0

Might be it's possible to use this workaround like checking an iterator against a NULL value:

#include <iostream>
#include <vector>


int main()
{
    std::vector<int> vector_int = {1, 2, 3, 4};

    std::vector<int>::iterator vector_int_iterator = --vector_int.begin();

    while(vector_int_iterator != vector_int.end())
    {
        if(vector_int_iterator == --vector_int.begin())
        {
            std::cout 
                << "Doing some stuff only once before iterate through vector's elements" 
                << std::endl;
            vector_int_iterator++;
        }

        std::cout << *vector_int_iterator++ << std::endl;
    }
}

It might be especially useful when we have to run while cycle more than once.

Vitalii
  • 53
  • 1
  • 7